Custom Post-Authentication in Federated Authentication Scenarios

With .NET Framework 4.5 the federated authentication is comparatively easy, the biggest work being preparation of the configuration file as a part of a setup.

“Plain” federated authentication

Basically, there is no need to write code, except modifications of the configuration file, and some onboarding process, which depends on authentication provider (aka identity provider, or STS)and may be very simple. Below is a short overview of what happens during federated authentication.

The application receives the first GET request from a user. The request is intercepted by the federation Http modules, then redirected to identity provider (STS), then redirected back to your application via POST from STS, where the POST carries some 50K encrypted authentication data. The federation Http modules decrypt and validate the authentication data, write authentication cookies (or references to cookies, which is the default in case of FX 4.5). Then the http authentication module triggers a redirect to itself, so that to receive almost original GET request, this time the request having been authenticated, as proved by authentication cookies. Further interaction between the client and your application goes on, for a while, without trips to STS. No code needs be developed to support the federated authentication as described above.

Post authentication

Now what if an application needs to perform additional checks, like custom validation of client certificates, or checks against the local AD (aka resource AD, or RP AD)?

Those post-authentication additional checks (steps) are such that if they fails, the whole authentication should be nullified. That is, the user’s authenticated status should be annihilated, and the STS should be notified so that it will forget the user.

By far the trickiest parts, in post-authentication, implementation-wise, are:

-          Where to insert the call to the post-authentication procedure (implementing the post-authentication checks), so that the checks will happen at the earliest possible time, when the very temporary status of being authenticated can’t do any harm.

-          How to prevent the federation Http modules from writing the authentication cookies into the response to the client. The cookies should be suppressed, even though the STS already endorsed (authenticated) the user.

Where to insert the post-authentication checks

As mentioned earlier, the STS authenticates the user, then redirects the auth data to the application via POST. The application receives the POST, the POST containing the set of claims from the STS. This set is packaged within an approx. 50 K blob. The only place to call post-authentication code is where the blob is decrypted (by Federation authentication infrastructure) and de-serialized. It is at this point the set of claims in clear is available to the post-authentication code.

So, where is this place? Well, it’s well hidden, and nearly impossible to find, unless you had the (quite a bit of) time to read most of ADFS documentation …

Derive from WSFederationAuthenticationModule

First, derive a class from the “System.IdentityModel.Services.WSFederationAuthenticationModule”, let it be FedAuthenticationModule:

public class FedAuthenticationModule : WSFederationAuthenticationModule

Override the OnAuthenticateRequest

Next, override the method, and, critically important, don’t forget to call the base class:
protected override void OnAuthenticateRequest(object sender, EventArgs e)
{
    base.OnAuthenticateRequest(sender, e);
}

Now the Magic Part

And it is here where the magic starts happening. The base.OnAuthenticateRequest calls the OnSessionSecurityTokenCreated, for which *you* also supply an override:
protected override void OnSessionSecurityTokenCreated(
  SessionSecurityTokenCreatedEventArgs args)

OnSessionSecurityTokenCreated

This override (callback, as old-timers name it) is the sweet spot to do additional authentication work. This is because it is at this point the authentication blob from the STS has been fully decrypted and deserialized into a set of claims, being now easily accessible from your supplementary post-authentication code.

In the OnSessionSecurityTokenCreated override, you call your post-authentication code:

var token = args.SessionToken;
var success = PostAuthChecks(token);

Here, depending on the success of the PostAuthChecks, either accept the almost-completed authentication, or force it to fail.

How to force the authentication to fail

Now, the STS already authenticated the user. We want, however, to hard-stop the authentication procedure “on track”, so that the user will a) never knows about success and b) cannot take advantage of authentication success, from the STS point of view.

In other words, we need to suppress the default behavior of the Session Authentication Module: write the authentication cookies to the response to the user.

How to Suppress Writing the Authentication Cookies

If the post-authentication checks fail, the best place to suppress writing the auth cookies into the response is right in the OnSessionSecurityTokenCreated override:

args.WriteSessionCookie = false;

The full code looks like below:

protected override void OnSessionSecurityTokenCreated(
  SessionSecurityTokenCreatedEventArgs args)
 {
     Wpp.Trace(Wpp.TL_VERBOSE, Wpp.TF_COMPONENT, "OnSessionSecurityTokenCreated");
     var token = args.SessionToken;
     var success = PostAuthChecks (token);
     if (success)
     {
         // one of 2 cases: 1) login and SC names match OR 2)
            names don't match, but after second connect to STS and filling in the name-
            matching SC into the
         // credentials dialog, the names became matching. Also case 3) - dev
            mode without certificate and without SC.
         Wpp.Trace(Wpp.TL_INFO, Wpp.TF_COMPONENT,
            "User successfully authenticated, will create the FedAuth cookie shortly");
     }
     else
     {
         // cases include:
         // 1) first connect to STS resulted in SC and login names mismatch
         // 2) all failure cases, including case when user failed
               to provide credentials on second connect to STS
         Wpp.Trace(Wpp.TL_WARN, Wpp.TF_COMPONENT,
            "OnSessionSecurityTokenCreated: Blocking creation of FedAuth cookie");
         // Suppress serializing the token reference into the cookie format,
            and attaching
            the cookie to the response
         args.WriteSessionCookie = false;
     }
 }