Mixing SSO with existing technologies (Part 5)

The finish line is in sight, let's sprint to the end of this project and finish it up! I apologize for the length of this series, but I truly hope that it helped a few people save some time and design a resilient authentication architecture. With that said, let's dive in and bring this whole architecture to the finish line.

Note: This is a continuation in a series of blog posts. If you haven't read the previous posts, I would highly recommend doing so to get up to speed.
I will be finishing up the solution, and you will need orientation for maximum value. Start with an overview of the problem and the approach we took to solving it in the first post: Part 1, or go farther back and make sure you are familiar with all of the terms in the introductory post here.

Ok, so last time we successfully authenticated using the web, and the built in WSFederation middleware. Now that we have authenticated for web based Single Sign On, let's look at how we can use the same SSO solution for processing API requests. We will leverage some of the same classes and functionality, but extend it to support an API based model.
This piece is a bit more complex than the web side, as we will have to build a little more of the pieces, unlike the web side where it was mostly provided for us.

Now while the web side required no application code changes to accept the token, the API side will. The WS-Federation middleware only understands cookies by default, and while you can use cookies for API's, it is generally preferred to use auth headers as they work better with the API paradigm. If you wanted to go the cookie route I imagine you could make that work relatively easily (and skip a lot of this post). However, we will go the auth header route and look at what that entails. In order to package the authentication into the headers, we will need a component to read the header, and then use it to authenticate with the application.

For the API we will want the token itself, not the full response like we used on the web side. Thankfully, we already have both pieces.

Let's start at roughly the same spot as last time, but go a bit of a different direction. In the client/legacy application we will need to set the request headers with the packaged token (referred to as a Bearer token because "Bearing" or holding it is all that is required to authenticate.)

var federationToken = WhatWeDidBefore;
var fedTokenXML = (federationToken as GenericXmlSecurityToken).TokenXml.OuterXml;

var bearerToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(fedTokenXML));
//Now add to the request header however you are calling the API
//"Authorization" : bearerToken

Now that the requests have the authentication header, we need to handle it on the new cloud application. To handle this we will utilize what is known as a DelegatingHandler to handle the authentication, and turn the token into an actual principal with usable security claims.

While you can read up plenty around the internet about how DelegatingHandlers work, the important thing to know is that they are added into the request pipeline, and can inspect and interact with both the request and the response from the rest of the pipeline. In some cases we might want to intercept, or alter all responses on the other side of the pipeline. However, for our purposes we only want to intercept the request, do something (authenticate) and then carry on to the rest of the pipeline, or if the authentication failed, return not authorized to the client.

So, how do we do this? First, we need a class that inherits from DelegatingHandler. Second, we need to override the SendAsync method, and be sure to call the base SendAsync when we are done and want the pipeline to continue. If the authentication fails we can simply return a result, and the rest of the pipeline will be skipped (which is perfect for not authorized).

I will post a full sample DelegatingHandler later, but for now let's look at what we need to accomplish in the SendAsync method. For now, let's look at a high level to keep it understandable.

First add a using for some extension methods to make validation easier.

using Microsoft.IdentityModel.Extensions;

Now in your SendAsync we need the following to occur:

//Get the Bearer Token from the Authentication header, and base64 decode it.
var token = GetAuthTokenFromHeader();

var tvp = GetTokenValidationParameters();

var handlers = GetSecurityTokenHandlers();

SecurityToken validatedToken;
//Here's the magic! use the extension method to validate the passed in token.
Try
{ 
  var validatedPrincipal = handlers.ValidateToken(samlTokenString, tvp, out validatedToken);

  //Perform any application claims transformations at this step.
  //You may, or may not need any transformations performed.

  SetCurrentPrincipal(validatedPrincipal);
}
catch (SecurityTokenValidationException)
{
  return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}

//CRITICAL! Make sure you call base to continue the pipeline. Anything that doesn't reach here will abort the processing pipeline at this step.
return await base.SendAsync(request, cancellationToken);

There were a few functionalities I collapsed into functions to express the functionality, but are critical to understanding how it works. Let's look at some of those now. First, I assume you can retrieve and decode the auth header, so let's skip that step. Next we have GetTokenValidationParameters which is used to get the details of how we want to validate the token. You can build this manually, use some integrated functionality to build it of the WSFederation metadata URL, or like us, let the OWIN middleware build it off the metadata, and then just consume it. So, where we register the WSFederation authentication for the rest of the web application, save off the options into a static field you can get to later. This makes both paths share, and makes it work together very nicely.

So, in your application startup do something like this:

var cancellationTokenSource = new CancellationTokenSource();
HttpClient httpClient = new HttpClient(new WebRequestHandler())
{
  Timeout = TimeSpan.FromSeconds(30),
  MaxResponseContentBufferSize = 1024 * 1024 * 5 //5MB
};

//Hit the WSFederation XML Metadata endpoint and bootstrap all of the configuration for us.
var mgr = new ConfigurationManager<WsFederationConfiguration>(metaDataAddress, httpClient);

var wsConfig = mgr.GetConfigurationAsync(cancellationTokenSource.Token).Result;

Startup.AuthOptions = new WsFederationAuthenticationOptions
{
    AuthenticationMode = AuthenticationMode.Passive,
    Wtrealm = SOME_REALM,
    Wreply = REPLY_TO_URL,
    Configuration = wsConfig,
};

Now just use that WsFederationAuthenticationOptions to setup your OWIN middleware, and then we will also use it in our handler.

To validate the incoming bearer tokens we will need some token validation parameters. To get these we will take the easy route, and ] mimic what the OWIN middleware does in our GetTokenValidationParameters function:

private TokenValidationParameters GetTokenValidationParameters()
{
    wsAuthOptions = Startup.AuthOptions;
    TokenValidationParameters tvp = wsAuthOptions.TokenValidationParameters.Clone();
    IEnumerable<string> issuers = new[] { wsAuthOptions.Configuration.Issuer };
    tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers));
    tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? wsAuthOptions.Configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(wsAuthOptions.Configuration.SigningKeys));

    return tvp;
}

Now that we have the parameters to use for validation, we need the handlers to wire the process up. Thankfully we can grab the SecurityToken Handlers off of the AuthOptions we save on App Start.

var handlers = Startup.AuthOptions.SecurityTokenHandlers;

Lastly we set the Principal in SetCurrentPrincipal. Due to some issues in how the principal gets set, we actually will want to set it twice. For details, you can refer to this post by Dominick. So our SetCurrentPricipal will look like this:

private void SetCurrentPrincipal(ClaimsPrincipal principal)
{
  Thread.CurrentPrincipal = principal;
  HttpContext.Current.User = principal;
}

Once the principal is set, you are home free! You should now be able to accept WSFederation authentication tokens in using the authentication headers, and utilizing the rest of the series stitch the whole process together into a working architecture!

I hope this series was helpful. This is unfortunately a confusing topic, and there isn't much information out there about this specific scenario. That was my driving force behind writing this series, as we struggled for longer than I would like to admit perfecting how this all flowed together.
If this series helped you, please let me know in the comments below, and share with others on Twitter and other Social Media! If you have suggestions, corrections, or just general comments, please let me know as well.

I will try to get more sample code posted on Github to show the overall flow more clearly as soon as possible. If you need a specific example before then, let me know in the comments, or on Twitter.
Thanks, and good luck!

Tim Ritzer's Image
Tim Ritzer
Missouri, USA

I am a Software Architect who loves to code, trying to practice what he preaches.
Follow me on Twitter @TimRitzer

Share this post