Office 365/ADFS 2.0: Forms AND Integrated Authentication (SSO) based on the user agent string

Background

The ADFS Farm + ADFS Proxy Farm model that we are using for Office 365 requires that the CNAME of the ADFS service has to be the same for both the ADFS proxy server farm and the internal ADFS farm (in our case adfs.ncl.ac.uk). Users ‘inside’ our network need to be directed to the internal farm and external users to the proxy farm.

ADFS supports multiple authentication mechanisms including the ones we are interested in, Windows Integrated Authentication (WIA) and Forms Based Authentication (FBA). It seems however that there is no way to dynamically select which one is used when a request hits the farm based on client properties. Where Office 365 is concerned a farm uses WIA or FBA

The way our network is configured means that we do not have the network model of Internal/DMZ/Internet with the split-brain DNS that the Microsoft documentation seems to expect. Our systems point at a single zone (running on BIND) which is resolves both internal and external requests.  As such, private IP addresses such as that of the internal ADFS Farm can be resolved (but obviously not connected to) from the Internet.

Working with our Network team we were able to get around this by creating a work around in BIND so that anyone on the Internet receives the address of the proxy farm and anyone coming from one of our internal IP ranges receives the address of the ADFS farm.

The problem for us is that only around 70% of our internal clients are domain joined and as such able to take part in SSO using WIA. The other devices may be non-Windows machines, non-domain joined Windows machines and mobile devices. Because they are coming from one of our internal address ranges they are directed to the internal WIA enabled ADFS farm and get a non-user friendly ugly pop-up box requesting authentication.

Authentication Popup

We do not think that this is a good user experience so we sought a solution which would let us provide both authentication methods to internal clients.

Possible solutions

After discussions internally and with Microsoft we were presented with 3 possible ways to deal with this problem.

  1. Our Network team could define every IP range we have and point them at the relevant BIND DNS view. This is obviously an inelegant solution and would not cover all scenarios as many ranges in our environment contain both domain joined and non-domain joined clients. It would however work for wireless guests as they are on specific ranges.
  2.  Microsoft proposed pushing out a HOSTS file to all domain joined clients pointing them at the internal farm. This not a scalable or suitable option in our environment as we have development work going on all over the University and this would essentially remove people’s ability to use the HOSTS file due to it being overwritten by whatever mechanism we would put in place to the job
  3. The third option was suggested by a Microsoft representative on the Office 365 community forums. The ADFS Farm could be configured to read a custom attribute from the browsers User agent string.This value would be parsed server-side and if present the request would be authenticated by WIA. Other requests would be forwarded on to FBA.  This was particularly attractive to us as we already use a custom user agent string value for Shibboleth authentication.

What we lacked was the expertise to implement this solution but thanks to collaboration with our colleagues as well as working with members of the Microsoft TechNet community we were able to implement something that seems to do the job for us. We thought we would share this in the event others are running in to the same problem!

Out of the Box Authentication with ADFS 2.0

The mechanism that is used by default on an ADFS farm or proxy Farm can be toggled in the <localAuthenticationTypes> element of the ADFS web.config

<microsoft.identityServer.web>
 <localAuthenticationTypes>
 <add name="Forms" page="FormsSignIn.aspx" />
 <add name="Integrated" page="auth/integrated/" />
 </localAuthenticationTypes>

For WIA ‘Integrated’ is at the top of the list:

<microsoft.identityServer.web>
 <localAuthenticationTypes>
 <add name="Integrated" page="auth/integrated/" />
 <add name="Forms" page="FormsSignIn.aspx" />
 </localAuthenticationTypes>

Implementing Selective Authentication using the user agent string

Manipulation of the User Agent string on Internet Explorer, Firefox and Chrome

The first thing required is to append the user agent string to browsers. This can be done in Internet explorer using Group Policy

  1. Under User Configuration expand Windows Settings/Internet Explorer Maintenance
  2. Select ‘Connection’
  3. In the right-hand pane, double-click User Agent String.
  4. On the User Agent String tab, select the ‘Customize String To be Appended To User Agent String check box
  5. Type in the string (in our case campus-ncl).

We have this value set in the ‘Default Domain Policy’ though it could be set lower down.

For Firefox and Chrome things have to be done in the application deployment package. Obviously people will have to use a managed version of the product as it’s not exactly a user friendly setup!

In Firefox the prefs.js file requires to extra lines:

user_pref("network.negotiate-auth.trusted-uris", "<ADFS FQDN>");
user_pref("general.useragent.override", ",<actual agent string> <customstring>")

So in our environment:

user_pref("network.negotiate-auth.trusted-uris", "adfs.ncl.ac.uk");
user_pref("general.useragent.override", ",<actual agent string> campus-ncl")

Chrome needs to be run with some extra switches:

--auth-server-whitelist="ADFS FQDN" --user-agent=" <actual agent string> + <customstring>

So in our environment

--auth-server-whitelist="adfs.ncl.ac.uk" --user-agent=" <actual agent string> + campus-ncl"

Disable Extended Protection must be disabled on the ADFS Farm in IIS (for Firefox and Chrome only)

In order to get SSO working with Firefox and Chrome Extended Protection must be disabled on the ADFS Farm in IIS. Lots of information on this feature and the consequences of disabling it can be found with a simple Google search.

ADFS Farm modifications

There are 2 steps required on the ADFS farm.

  1. Enable Forms Based Authentication as the default method.
  2. Modify the FormsSignIn.aspx.cs source code file

To turn on FBA edit the <localAuthenticationTypes> element of the ADFS web.config file and make sure FBA ‘Forms’ is at the top of the list:

<microsoft.identityServer.web>
 <localAuthenticationTypes>
 <add name="Forms" page="FormsSignIn.aspx" />
 <add name="Integrated" page="auth/integrated/" />
 </localAuthenticationTypes>

Next open the FormsSignIn.aspx.cs Source Code File.

The default out of the box, the code looks like this:

using System;

using Microsoft.IdentityServer.Web;
using Microsoft.IdentityServer.Web.UI;

public partial class FormsSignIn : FormsLoginPage
{
 protected void Page_Load( object sender, EventArgs e )
 {
 }
…

We need to add some code to the Page_Load event which will forward the request to integrated authentication if the campus-ncl user agent string is present. In order to do this we had to add System.Web to the namespace list.

using System;
using System.Web;
using Microsoft.IdentityServer.Web;
using Microsoft.IdentityServer.Web.UI;

System.Web supplies the classes that enable browser-server communication which are needed to get the user agent string and the query string generated by Microsoft Online Services.

protected void Page_Load( object sender, EventArgs e )
 {
 //Get the raw query String generated by Office 365
 int pos = Request.RawUrl.IndexOf('?');
 int len = Request.RawUrl.Length;
 string rawq = Request.RawUrl.Substring(pos + 1, len - pos - 1);

 //Convert query string (qs) to a string
 string qs = HttpUtility.ParseQueryString(rawq).ToString();

 //Get the user agent value
 string uagent = Request.UserAgent;

 //Check if the string campus-ncl appears in the User Agent
 //If it is there forward to WIA along with the Query String

 if(uagent.IndexOf("campus-ncl") > -1)
 {
 Response.Redirect("/adfs/ls/auth/integrated/?" + qs, true);
 }
 else
 {
 //Carry on and do Forms Based Authentication
 }
 }

And that’s it! Anyone using a managed browser with the custom string will be forwarded for WIA and get the SSO experience and all others will get FBA.

Things to note

  1. This method is not officially supported by Microsoft and there are potential issues around future ADFS upgrades (there is no guarantee that the same configuration will be in future versions of ADFS). We are also developing the fall back plan of pointing different clients and the different farms in DNS in case it is needed.
  2. There may very well be a better way to do this! If you find one please let us know 🙂

Special mention

Although we knew what we wanted to do we were having trouble getting the query string and putting it in a usable form (I’m not a programmer!) This information was provided by another TechNet forum member

 

This entry was posted in ActiveDirectory, Cloud, Exchange, WindowsServer by James. Bookmark the permalink.

About James

I am an Infrastructure Systems Administrator in the Infrastructure Systems Group (ISG) within ISS. We are responsible for a number of the core services which support the IT Infrastructure of the University including Active Directory, Exchange, DNS, Central Filestore, VMware and SQL. I hold number of current Microsoft Certifications and am also a Symantec Certified Specialist (Netbackup) http://twitter.com/JamesAPocock

13 thoughts on “Office 365/ADFS 2.0: Forms AND Integrated Authentication (SSO) based on the user agent string

  1. Pingback: Customer Story: Achieving consistent SSO with AD FS 2.0 and Office 365 for education - UK Live@edu Blog - Site Home - MSDN Blogs

  2. Hello, I was directed to this blog via the Office365 education Blog. We have a similar setup, and information has helped me tremendously. I wondered if you’d managed to get this to work with Windows 8 RP (IE10) as the usual method for modifying the user agent via GP isn’t possible in IE10, and the old method of adding a string to the registry doesn’t appear to work either.
    Thanks for the information.
    Martin.

  3. Thanks for your comments Martin. We haven’t look at this yet but certainly will when Windows 8 is released in a few weeks’ time. I would hope that Microsoft will release some updated GPO settings for this but we’ll see. If you get anywhere before we do please let us know!

    James

  4. Hello both

    We haven’t formally tested it but my colleague Jon Noble is running the RTM of Windows 8 and the custom user agent string is present in both IE10 regular and in IE10 Metro. We set it in a GPO

    User Configuration (Enabled)> Policies > Windows Settings > Internet Explorer Maintenance > Connection > User Agent String > Custom string to be appended to user agent string

    James

    • Just a note that Internet Explorer maintenance is gone from the Windows 8 / Server 12 tools so you can’t set it that way anymore.

      You would need to use a Win7 Client to make thw changes, but once you have moved the ADMX files from Windows 8 to the central store there is a chance you cannot use Win 7 admin tools anymore (like the changes from Vista to 7)

  5. I was experiencing a similar problem at my University and stumbled upon this post. This information is fantastic! However I opted to go with a variation of “Possible Solution 2″ (pushing out a HOSTS file to all of the domain joined computers). But rather than push out a HOSTS file that could potentially overwrite an already customized file, I wrote a simple batch script to scan the computer’s existing HOSTS file and append the WIA Farm IP if it’s missing. The script is defined in Group Policy under Computer > Policies > Windows > Scripts > Startup.

    So now our DNS server provides non-domain joined devices with the IP of the ADFS proxy farm (FBA) and domain joined machines use the value injected into its HOSTS file to get to the non proxy farm (WIA). I haven’t found a downside to this method yet. Hopefully someone else will find this useful. IP and FQDN values need to be changed for your environment. Mind the word wrap.

    @ECHO OFF
    CLS

    REM — Check to see if the local hosts file has an entry for 111.111.111.222 adfs.domain.edu
    REM — Used regex in case users modify their HOSTS file and change the whitespace between IP and FQDN
    findstr /x /r /i /c:”^111\.111\.111\.222 *adfs\.domain\.edu” %windir%\System32\drivers\etc\hosts > NUL

    REM — If it does not exist, add it
    IF %ERRORLEVEL% NEQ 0 (
    echo. >> %windir%\System32\drivers\etc\hosts
    echo 111.111.111.222 adfs.domain.edu>> %windir%\System32\drivers\etc\hosts
    )

  6. Great Blog! One thing I’m going to test is to adopt the same approach but use an F5 load balancer, this way you don’t have to tweak the code at source and get the F5 to direct user traffic based on the user-agent string.

  7. Thanks for this post, it’s excellent. One question – what authentication settings did you use for the site/v-dirs in IIS? The ‘Default Web Site’, ‘adfs’ and ‘ls’ Virtual Directories?

    For myself, when ‘Anonymous Authentication’ and ‘Windows Authentication’ are both enabled the FBA login works, and the redirect works, but the WIA page errors out with “msis7000: The sign in request is not compliant to the WS-Federation language for web browser clients or the SAML 2.0 protocol Web SSO profile.”. Disabling Anonymous login fixes this for WIA logins but non-WIA logins (external users, phone users etc) get repeated password prompts in the ugly Dialog box

    Thanks
    Regards,
    Sam Miller

  8. We had a similar issue its possible to determine the Windows machines from the UserAgent string because they have WinNT in the string IE 10 passes the Domain in the User Agent string, IE 11 does not. This also worked for chrome and the other browsers on a Windows machine authenticating them as AD whilst switching various phones and tablets and Macs to Forms Authentication. Chrome you need to configure the site as trusted for this to Work on a Windows machine. Our scenario is this was the testing code for the adfs farm on the internal network the one in the DMZ handles the devices on the internet. We set the adfs forms Website to anonymous authentication. This code will allow exploration of the options for your network.

    using System;
    using Microsoft.IdentityServer.Web;
    using Microsoft.IdentityServer.Web.UI;
    using System.Web;
    using System.Security.Principal;

    ///
    /// Attempts to authenticate the user via HTTP Forms Authentication.
    /// based on code from here https://blogs.ncl.ac.uk/isg/?p=296
    /// Author : Jason Robertson
    /// Date : 13/12/2013
    ///
    public partial class FormsSignIn : FormsLoginPage
    {
    protected void Page_Load(object sender, EventArgs e)
    {

    //Get the raw query String generated by Office 365
    int pos = Request.RawUrl.IndexOf(‘?’);
    int len = Request.RawUrl.Length;
    string rawq = Request.RawUrl.Substring(pos + 1, len – pos – 1);

    //Convert query string (qs) to a string
    string qs = HttpUtility.ParseQueryString(rawq).ToString();

    ////Get the user agent value
    string uagent = Request.UserAgent;

    WindowsIdentity userIdentity = Request.LogonUserIdentity;
    System.Web.HttpBrowserCapabilities browser = Request.Browser;

    // OS Versions and Platform Manufacturer
    string deviceType = “”;
    string manufacturer = “”;

    if (uagent.ToLower() != null)
    {
    if (Request.Browser.IsMobileDevice == true)
    {
    deviceType = “Mobile”;
    }

    if (uagent.Contains(“iphone”))
    {
    deviceType = “Mobile”;
    manufacturer = “Apple”;
    }
    else if (uagent.Contains(“blackberry”))
    {
    deviceType = “Mobile”;
    manufacturer = “Blackberry”;
    }
    else if (uagent.Contains(“mobile”))
    {
    deviceType = “Mobile”;
    manufacturer = “unknown”;
    }
    else if (uagent.Contains(“windows ce”))
    {
    deviceType = “Mobile”;
    manufacturer = “Windows CE”;
    }
    else if (uagent.Contains(“opera mini”))
    {
    deviceType = “Mobile”;
    manufacturer = “Oper Mini”;
    }
    else if (uagent.Contains(“palm”))
    {
    deviceType = “Mobile”;
    manufacturer = “Palm”;
    }
    else if (uagent.Contains(“android”))
    {
    deviceType = “Mobile”;
    manufacturer = “Android”;
    }
    else if (uagent.ToLower().Contains(“windows”))
    {
    manufacturer = “Microsoft”;
    }
    else
    {
    manufacturer = “Computer”;
    }
    }

    string platform = browser.Platform;

    // Domain if its annonymous not AD authenticated runs under the IIS_IUSR account machine name which does not contain the kordamentha domain

    if (uagent.ToLower().Contains(“kordamentha”) || userIdentity.Name.ToLower().Contains(@”kordamentha\”)
    || platform.ToLower().Contains(@”winnt”))
    {
    Response.Redirect(“/adfs/ls/auth/integrated/?” + qs, true);
    }
    else
    {
    // load this page as normal
    }
    }

    protected void HandleError(string message)
    {
    ErrorTextLabel.Visible = true;
    ErrorTextLabel.Text = Resources.CommonResources.IncorrectUsernameText;
    }

    protected void SubmitButton_Click(object sender, EventArgs e)
    {
    try
    {
    SignIn(UsernameTextBox.Text, PasswordTextBox.Text);
    }
    catch (AuthenticationFailedException ex)
    {
    HandleError(ex.Message);
    }
    }
    }

  9. Firstly, thanks for the great blog – we’ve found your ideas very useful in tackling this irritating issue. We were looking to deploy either the user-agent workaround or the host file fix. But according to Technet (http://technet.microsoft.com/en-us/library/dn554246.aspx), there is now native support for this in ADFS 3.0:

    “Support for automatic fallback to forms-based authentication for devices that are not joined to the corporate domain but are still used generate access requests from within the corporate network (intranet).”

    Yet I cannot find any further documentation on how to achieve this. By default, this doesn’t work in our lab environment. Has anyone managed to get this working in Windows 2012 R2? It’s annoying how MS assume everyone has split DNS..

  10. Hi Duncan,

    This does work in ADFS 3.0, the internal farm fails back to FBA for non-domain joined clients. You may see the basic pop up behaviour if you haven’t added your federation endpoint URL into your local intranet sites ie sts.contoso.com.

    Good luck!
    David

  11. So I’m working with a ADFS 2.1 server (2012) and would like some clarity on adding the last part.

    Do you replace or just add it in at the beginning of the sub?

Leave a Reply

Your email address will not be published. Required fields are marked *