Mixing WS-Federation and Windows Authentication in IIS

Imagine, you have an ASP.NET web application in IIS accessed by:

  • / — everyone
  • /orders — customers, authenticated with federated SSO
  • /admin — personnel, authenticated with Active Directory

How to configure this?

Ugly solution

For such kind of auth-mixing the internet suggests the following algorithm:

  1. In the controller of /admin, if a client is not windows authenticated then return response with status code 418, else do the normal job.
  2. In global.asax.cs in Application_EndRequest() method, if the status code is 418, set it to 401.2 to provoke a challenge by an authentication module next in a pipeline.

Simple, but:

  1. Windows authentication performed as a result of such 401 substitutions would be NTLM, but never Kerberos.
  2. It doesn’t consider the case of user walking from personnel URL to customer URL and back, having customer’s fed-auth cookies and Kerberos / NTLM authentication at the same time. How would principal corresponding to URL be reconstructed?
  3. It relies on fake code instead of some kind of authorization exception or filter attribute. This spoils structure of the code.
  4. It relies on the order of EndRequest event handling in the pipeline. There’s no documented guarantees about that order.

I believe a double response status code change is the dirty hack.

Correct solution

To get some general understanding read my IIS internals article, especially final parts about federated authentication.

Create some configuration section to put the collection of URLs (admin URLs used by personnel) in it, for which your skip federated and go for windows authentication:

<federationAuthenticationExclusions>
    <items>
      <add url="/admin" />
      <add url="/debug" />
    </items>
</federationAuthenticationExclusions>

Of course, you need to support this by section / collection / item objects in code. See example.

Extend WSFederationAuthenticationModule to ignore handling of status code 401 for admin URLs:

using System;
using System.Collections.Generic;
using System.IdentityModel.Services;
using System.Linq;
using System.Web;
using WebApplication1.Configuration.FederationAuthenticationExclusions;

namespace WebApplication1.Modules
{
    public class FederationAuthenticationModule : WSFederationAuthenticationModule
    {
        protected override void OnEndRequest(object sender, EventArgs args)
        {
            HttpApplication httpApplication = (HttpApplication)sender;

            // step over federative authentication if URL in federationAuthenticationExclusions section of Web.config
            foreach (Item item in Section.Default.Items)
            {
                if (httpApplication.Request.RawUrl.StartsWith(item.Url, StringComparison.InvariantCultureIgnoreCase))
                    return;
            }
            base.OnEndRequest(sender, args);
        }
    }
}

Extend SessionAuthenticationModule to not just skip for admin / personnel URLs, but also to clean httpApplication.Context.User up if some windows authenticated one is visiting federated URLs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading;
using System.Web;
using WebApplication1.Configuration.FederationAuthenticationExclusions;

namespace WebApplication1.Modules
{
    public class SessionAuthenticationModule : System.IdentityModel.Services.SessionAuthenticationModule
    {
        protected override void OnAuthenticateRequest(object sender, EventArgs eventArgs)
        {
            HttpApplication httpApplication = (HttpApplication)sender;

            // step over federative authentication if URL in federationAuthenticationExclusions section of Web.config
            foreach (Item item in Section.Default.Items)
            {
                if (httpApplication.Request.RawUrl.StartsWith(item.Url, StringComparison.InvariantCultureIgnoreCase))
                    return;
            }

            IIdentity identity = Thread.CurrentPrincipal.Identity;

            // in case of federative authentication (URL not in exclusions)
            // if user is authenticated, but it is not federative authentication, reset authentication
            if (identity.IsAuthenticated || identity.AuthenticationType != "Federation")
            {
                httpApplication.Context.User = null;
            }

            // if user is not authenticated, try to authenticate as usual by FedAuth cookie
            base.OnAuthenticateRequest(sender, eventArgs);
        }
    }
}

If a user goes to admin / personnel URL then to customer page, session module will try to reconstruct principal from fed-auth cookie normally.

If a user goes to customer page then to admin / personnel URL, session module will skip reconstruction, and normal windows authentication will happen.

The working example you can take at https://github.com/dmlarionov/IISMixedAuthExample.

One thought on “Mixing WS-Federation and Windows Authentication in IIS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: