NTLM Integrated SSO for SAML with the APM module and an External Logon Page

Leveraging the flexibility of the F5 APM module, this solution extends the ability to single sign on using integrated credentials. This is currently possible by installing the various browser based F5 APM plug-ins; this solution is back end based so no need to touch the client, it also fails back to basic authentication. Included is a simple ASP.NET web site that will take care of the authentication. When using an external logon page with APM, the module expects a username and password. In this example, we post back a dummy password once the user is authenticated.

Now this dummy password thing can’t be secure right? Of course it isn’t. Using cryptographic methods native to .Net and the F5 interpreter, we can validate the sign on with simple AES encryption algorithms. AES uses an initialization vector (starting vector) to randomize the encrypted output, you want to do this to make it that little bit harder to break. Below in figure 1.1 is the workflow designed in the VPE (Visual Policy Editor). So what happens is: the user lands at the cloud service, at logon request the browser is redirected to your  IDP (SAML identity provider). The IDP passes the request to your own internal web site, validating the user using with NTLM authentication. It creates an encrypted string which gets sent back to the APM and presto, password-less authentication! All of this in milliseconds.

Figure 1.1

Ready to begin?

The web site Visual Studio project can be downloaded @ ASP.Net External Logon Page Project

1. Visual Studio instructions:

- Open the project and open up the default.aspx.cs set the AesKey string to a random 16 byte value.

- Now find the string just above AesKey and insert a breakpoint, now run a debug in Visual Studio once you hit the breakpoint, hit F11 and then highlight string keyme = BitConverter.ToString(aes.Key) – this is your key in Hex format, you will need to record this it will come up in part 2!

- Open DoPost.aspx.cs and locate the url string, now set this to match the site of the F5 SSO Identity Provider URL, note the familiar /my.policy

- Open the project and click to publish it to your web server. Configure your HTTPS certificate bindings as you wish, using a wildcard certificate works great, the website could be called sso.<yourcompany>.com

- In IIS Manager, under authentication enable Integrated authentication and ASP.Net Impersonation only

- Locate the web.config file and make sure you paste the following in the file:


<validation validateIntegratedModeConfiguration=”false” />

</system.webServer> <

- Go to your application pool for the website and set the application pool identity to a regular domain service account, restart the web site.

That’s it – now you should have your SSO web site ready to serve!

2. F5 iRules

Right, almost there. So next up we need to configure the F5 to be able to capture the data the newly created webpage is posting to it. Hail to the lighting quick iRule. Here the plan is for the virtual server you use as the IDP to capture the posted data from your web server and allow the user to send their personally signed SAML assertion to your cloud service provider. You will notice a lot of logging events, this is more for illustration / debugging purposes. You can certainly remove or change to your liking.

- Get the hex key you recorded in the Visual Studio instructions and strip it of all hyphens. You want to end up with a 32 bit value like “32524742265948265A554A4D3349243C”

- Create your irule with the following:


    when RULE_INIT {
      #Set your hexkey that you got from the debug in Visual Studio
      set static::hexkey “32524742265948265A554A4D3349243C”
      ##We want to do this so that we can assign our F5 session variables based on the payload
      ACCESS::restrict_irule_events disable
    when HTTP_REQUEST {
      # Check for post requests to /my.policy
      if {[HTTP::uri] starts_with “/my.policy” && [HTTP::method] eq “POST”}{
        # Collect up to 1Mb of request content
        if { [HTTP::header exists "Content-Length"] && [HTTP::header "Content-Length"] < 1048577 } {
          set content_length [HTTP::header "Content-Length"]
        } else {
          set content_length 1048576
        if { $content_length > 0 } {
          HTTP::collect $content_length
      # Parse the details from the collected payload
      log local0. “Payload = [HTTP::payload]“
      set username [URI::query "?[HTTP::payload]” username]
      set password [URI::query "?[HTTP::payload]” password]
      set service [URI::query "?[HTTP::payload]” service]
      set token [URI::decode [URI::query "?[HTTP::payload]” token]]
      set vector [URI::decode [URI::query "?[HTTP::payload]” vector]]
      log local0. “Username = $username Password = $password Token = $token vector = $vector”
      ACCESS::session data set session.logon.last.password $password
      ACCESS::session data set session.logon.last.username $username
      ACCESS::session data set session.custom.token $token
      ACCESS::session data set session.custom.service $service
      set text_to_encrypt $username
      set enc_out_no_binary [CRYPTO::encrypt -alg aes-128-cbc -keyhex $static::hexkey -ivhex $vector $text_to_encrypt]
      set dec_in [CRYPTO::decrypt -alg aes-128-cbc -keyhex $static::hexkey -ivhex $vector $enc_out_no_binary]
      log local0.info “Client IP is : [IP::client_addr]“
      log local0.info “The decrypted NO binary $dec_in”
      log local0.info “The Encrypted NO binary Base64 is: [b64encode "$enc_out_no_binary"]“
      binary scan $enc_out_no_binary H* enc_hex
      log local0.info “The Encrypted NO binary Hex is: $enc_hex”
      if { $token == [b64encode $enc_out_no_binary]} then {
        log local0.info “Houston we have a match for: $username”
        ACCESS::session data set session.custom.keyauthenticated “true”


3. The F5 Visual Policy Editor Access Policy

Almost there.

Now you probably already have an access policy configured, below is what we did as it suited our environment and user requirements. With the advent of bring and choose your own device, it is becoming increasingly rare for standardized computers or browsers. The users that will benefit most from this solution, however, are the domain joined clients (including Macs or Samba domain joined clients) using browsers like Internet Explorer and Chrome; they will experience a true password-less, pass-through experience.

An example of the Access Policy Workflow.

- So first step is to start with an access policy and add an external logon page, point this to your website that you published in Step 1. so this could be something like https://sso.helterskeltercompany.com (hint – you can also choose to specify a URI of /service=dropbox or /service=googleapps; this has been catered for in dopost.aspx.cs in case you want to post back to a different virtual server. This however, is not necessary to get you going)

- Create an empty box with a branch rule that has a custom expression checking for the session variable you created in Step 2 for Key Validation. Enter the following value for the custom expression: expr { [mcget {session.custom.keyauthenticated}] == “true” }

- Now you are probably already querying some form of a directory service, be it LDAP or Active Directory.  An email address is typically needed so that this can be used for a SAML assertion.

For additional security, we want to make sure that the user is not able to formulate an HTTP post to your IDP should they no longer be in the employ of your company. In the case of Active Directory, ensure the user object is enabled and does not have any flag set marking it as disabled, so the attribute here that flags it is userAccountControl. In the AD query step create a final branch rule setting the following to make sure the account is a normal account and not disabled – “expr { [mcget {session.ad.last.attr.userAccountControl}] == “512″ }

That’s it!

Published Feb 26, 2014
Version 1.0

Was this article helpful?

1 Comment

  • This looks really promising and exactly what I am looking for. Unfortunately, the ASP script in Step 1 is a bad link, now. Any chance you could post that project somewhere and edit the link?