Office 365 Logon Enhancement – Username Capture

Introduction

With the new Office 365 sign-in experience you can capture the username entered at the O365 login page! This has been a frequent request from Office 365 users federated via SAML so users don’t have to enter their username twice when performing SP initiated logon and getting redirected from O365 for authentication.

Using the new O365 Sign-In Experience

Here’s the new sign-in experience I’m talking about:

If you try the new sign-in experience, it will post the username along with the SAML AuthN request. That’s what we need to prefill the username field for them. Once you try it, a cookie is set and you’ll continue to use it unless you click the link on the sign-in page to use the old sign-in experience.

Improving Your User’s Logon Experience

(Updated iRule should work with all browsers)

when HTTP_REQUEST {
    # Check for for SAML POSTS
    if { [HTTP::uri] starts_with "/saml/idp/profile/redirectorpost/sso" && [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
        }
    }
}

when HTTP_REQUEST_DATA {
    # Parse the username from the collected payload
    set username [URI::decode [URI::query "?[HTTP::payload]" username]]
    HTTP::release
}

when ACCESS_SESSION_STARTED {
    if { [ info exists username ] } {
        ACCESS::session data set session.logon.last.username $username
    }
}

Now you’ll need to customize your access policy. You will need to replace your existing Logon Page object with a new macro. The is the end result we’re looking for:

First is the Username Check. You’ll need to add an empty object (click +, general purpose tab, empty). You want to create a branch rule, use advanced, and enter the following:

expr { [mcget {session.logon.last.username}] ne "" }

Next, the AD Query (authentication tab). The purpose of this object is to find the matching AD account for the email address that Office 365 sends. Normally it will match your user’s UserPrincipalName (UPN), so you can see we’ve customized the search filter to look for a matching UPN instead of the default which is a matching sAMAccountName.

If that’s not the case, you can change the search filter here, for instance yours might match the mail attribute if you’ve not modified your UPNs as is standard practice for O365. Also note that I’ve changed the branch rule, we don’t want the default of checking the primary group ID. We just want to know if the query was successful (did we find a match).

Here's what you'll enter into the search filter assuming you have set UPN to match O365 email.

userPrincipalName=%{session.logon.last.username}

If all your sAMAccountNames are just the first part of the email address, you can leave out the AD Query, Variable Assign, and Logon Page – Username Found But Invalid objects and just check the box for “Split Domain Name From Full Username” on the Logon Page – Username Found object.

 

Next, the variable assign (assignment tab). This object takes the sAMAccountName we got from the AD Query and puts that value into the username variable. Put session.logon.last.username on the left side, and on the right side select AAA attribute, AD, Use user’s attribute, and the attribute name is sAMAccountName.

Finally, the logon page (logon tab) for when O365 sends us the email and we find a matching username. Note that it’s just like your normal logon page, but you set the “Read Only” value to yes for the username field. This will cause the matching username to show up there.

Notice there are two other logon pages there, one for when no username is found and one for when there is no AD match to get a sAMAccountName for. Those are standard, nothing special. I’ll include pictures here anyway.

And now when you logon using the new O365 sign in experience, this is what you’ll see:

Give it a try, you'll make your users happy!

Published Aug 10, 2017
Version 1.0

Was this article helpful?

5 Comments

  • AP's avatar
    AP
    Icon for Nimbostratus rankNimbostratus

    Hi,

    I also found that signing in via a different Microsoft landing page results in a different POST request to the Idp. Different again from the new and old sign-in experience.

    The particular landing page I came across was office365.com, which redirects you to products.office.com. If you click sign in from here, the sign-in page looks very similar to the O365 new sign-in experience, however it's actually login.live.com.

    The POST request to the IDP still has a referer of login.microsoftonline.com, however the username parameter doesn't exist. Fortunately however, the username is available in the Referer header with "login_hint=". The iRule below is based on Grahams with the added functionality of being able to grab the login_hint username when available.

    The iRule is working in a Dev environment for these use-cases, however there may be others. By default these will still require manual input of username on the Idp. It could also be possible to make the iRule more efficient by putting the new sign in experience conditional statements (payload capture) first, assuming it is the most common sign-in method for a given client base.

     The following iRule attempts to capture the Username for Office365 SAML SP initiated authentication requests
     to allow pre-population of username on logon page. This iRule requires specific logic to be built in the VPE.
    
    when HTTP_REQUEST {
     Enable/disable iRule debug logging.
    set o365usernamedebug 0
    
         Check for for SAML POSTS  
        if { [HTTP::uri] starts_with "/saml/idp/profile/redirectorpost/sso" && [HTTP::method] eq "POST" } {
    
            if {$o365usernamedebug} {log local0.info "Request is a SAML POST"}
    
             For logins via certain Microsoft landing pages - Check if Referer header contains login_hint
    
                        if { [HTTP::header exists "Referer"] } {
    
                if {$o365usernamedebug} {log local0.info "POST Request has a Referer header"}
    
                    set refererHeader [HTTP::header "Referer"]
    
                    if { $refererHeader contains "login_hint="} {
    
                    set username [URI::decode [URI::query $refererHeader login_hint]]
    
                    if {$o365usernamedebug} {log local0.info "Referer header contains login_hint. Username = $username "}
    
            }
    
                unset refererHeader         
    
        }
    
         For logins via new sign-in experience - Collect Payload to enable a search for a username parameter
    
        Collect up to 1Mb of request content
    
        if { ![ info exists username ] } {
    
            if {$o365usernamedebug} {log local0.info "login_hint not available. Looking for username in payload"}
    
            if { [HTTP::header exists "Content-Length"] && [HTTP::header "Content-Length"] < 1048577 } {
    
                set contentLength [HTTP::header "Content-Length"]
    
            } else {
    
                set contentLength 1048576
            }
    
            if { $content_length > 0 } {
    
                HTTP::collect $contentLength
    
            }
        }
    }
    }
    
    when HTTP_REQUEST_DATA {
    
         For logins via new sign-in experience - check payload for username parameter
    
        if { [ info exists contentLength ] } {
    
             Parse the username from the collected payload
    
            if { [HTTP::payload] contains "username="} {
    
                set username [URI::decode [URI::query "?[HTTP::payload]" username]]
    
                if {$o365usernamedebug} {log local0.info "Username parameter exists. Username = $username "}
    
                HTTP::release
    
            }
        }
    }
    
    when ACCESS_SESSION_STARTED {
    
        if { [ info exists username ] } {
    
            ACCESS::session data set session.sso.custom.o365mail $username
    
        }
    }
    
  • AP's avatar
    AP
    Icon for Nimbostratus rankNimbostratus

    Hi Songseajoon,

     

    It will be the AD Server that you want your SAML IdP to use. If you have self-hosted AD and your F5's are in the same location (e.g. on-premise), then you'd generally be looking at using those AD servers.

     

    You probably want to talk to your AD team if these concepts are unclear.

     

  • Great post!

    Slight modification if being redirected from mail.office365.com. This will capture the username if found and write it to the session.logon.last.username only if the URI and referrer are matched. You can then use this in VPE as described above to pre-populate username

    when HTTP_REQUEST {
     if { [HTTP::uri] starts_with "/adfs/ls/" } {
          if { [HTTP::header exists "Referer"] && [HTTP::header "Referer"] contains "office365.com" } {
               set received_requesturl [HTTP::uri]
               log local0. "Starting GetURIUsername"
               log local0. $received_requesturl
               if { $received_requesturl contains "username="} {
                    log local0. "requested URL has username"
                    set username [URI::decode [URI::query $received_requesturl username]]
                    log local0. "Username detected as $username"
               }
               unset received_requesturl
           }
     }
     }
    
    when ACCESS_SESSION_STARTED {
    if { [ info exists username ] } {
        ACCESS::session data set session.logon.last.username $username
        log local0. "Username set to $username"
    }
    }
    
  • Hello Graham,

     

    Thanks for you article.

     

    In your case F5 is IDP, check now the use case where F5 is SP, it is easier because Azure IDP support a "login_hint" as a query parameter. By adding it on F5 SAML Request it allows you to bypass the Azure Login Page.

     

    https://devcentral.f5.com/s/articles/Bypass-Azure-Login-Page-by-adding-a-login-hint-in-the-SAML-Request?page=1

     

    Regards