Mixing SSO with existing technologies (Part 2)

Ok, so last time we discussed the grand plan. The pie in the sky hopeful architecture to solve a distributed, and yet centralized authentication system that would allow us to combine the old and the new. This time we start actually solving the problem with examples and code.

Let's take a quick moment to refresh ourselves on the target architecture.

Note: This is a continuation in a series of blog posts. If you haven't read the first two, I would highly recommend doing so to get up to speed.
I will be diving right into solutions which will make much more sense with the right groundwork. Orient yourself with an overview of the problem and the approach we took to solving it in the last post: Part 1, or start with some terms in the introductory post here.

First, we will have the legacy application, and an IdP token provider (in our case ADFS) both on premise as needed. This allows the existing authentication providers like Active Directory to be used, and the authentication can happen all within the bounds of the client environment.

Next, we will have a federation IdP token provider (in our case Azure ACS) as well as the new application both hosted in the cloud (In our case Microsoft Azure)

Lastly, the legacy application will be communicating with the new, cloud based application via APIs, as well as facilitating SSO access between the two without requiring user interaction.

So that all sounds great, but how about we start getting into the actual details of the implementation.

First of all the protocol we will be using is WS-Federation. It is a bit of an Enterprise kind of protocol, and may be too heavyweight for some uses. Some of the more lightweight protocols like OAuth may be a better fit for your problem. For us it was a no brainer, WS-Fed provides the features and architecture that really fit with our use case.

WS-Fed is a part of the Web Services Security framework, a security framework originally designed to complement SOAP web services. It is a set of extensions onto another Web Services Security protocol called WS-Trust. The extensions it adds are to allow multiple security domains to cooperate and provide federated identities. In our scenario, it provides the mechanism for the IdP token provider (ADFS) to cooperate with the federation token provider (ACS). It also provides a passive authentication method, that while not strictly required for the final architecture, is extremely helpful for facilitating logins using a web browser. In our example, it has a nice side effect of providing a direct logins to the cloud application when not integrating with the legacy application.

The first step in our target architecture is to log-in to the Identity Provider, and retrieve the token from that service. In order to do that, we need to know where the identity provider is, or more specifically, which identity provider we want to use. The federation token provider has many, many trusted identity providers, and while all would work for the application, the user will have an account on one specific provider.
The act of discovering where to log into in a federation environment is known as Home Realm Discovery.

We got lucky in this regards, as we do not need any kind of sophisticated realm discovery. Since everything is on premise, we can simply configure the relationship directly. If for some reason this is not the case in your scenario, you will need to develop a system to perform this discovery. There are many different approaches ranging from using the user's e-mail domain to performing IP address range lookups. I won't dive into this, especially since every scenario will vary in what the best solution is.

Because the WS-Trust/WS-Fed protocols build from the SOAP Web Services world it should come as no surprise that the classes to utilize this process manually come from WCF namespaces. Despite this, there is absolutely no requirement that WCF itself be used for anything other than performing the authentication. So have no fear, you can have a clean, light, REST API and a clean web integration and only use WCF to perform the authentication itself.

The first part of the authentication process is to perform a WSTrust request for a token from the Identity Provider token server. For that we need an instance of WSTrustChannelFactory. Note the namespace (System.Servicemodel.Security), as in .Net 4.5 with the refinement of the WIF functionality, a lot of the fundamental classes moved around. Make sure to use the new assemblies, or you will pay the price down the road with compatibility issues.

using (var channelFactory = new WSTrustChannelFactory(
            new UserNameWSTrustBinding(),
            new EndpointAddress(new Uri(StsEndpoint))))
{
    .... 
}

Ok, so we created a new WSTrustChannelFactory for us to create the channel needed to perform the request from. We defined that we would be using a username and password to authenticate, and then provided an address for the endpoint we are authenticating against. In our case the URL would be something like:

StsEndpoint = @"https://SERVER/adfs/services/trust/13/usernamemixed";

Which gives us the ADFS WSTrust endpoint that will provide a token in response to a username and password, which is exactly what we want.

Unfortunately, there is one caveat. The UserNameWSTrustBinding is from the old set of WIF classes, and has no direct, easy replacement in the new WIF classes in 4.5. I am entirely unsure why, but thankfully Dominick Baier has rewritten the class, and made it compatibable with the .Net 4.5 WIF namespaces. You can find his class here, and the base class for it here. You will need those both for the rest of this post to work.

We ended up stripping down those classes to eliminate all of the handling of scenarios we did not need. It made a big difference in the readability of the code for us. If you are curious what our final versions looked like, you can find them here UserNameWSTrustBinding, and the base class

Next we need to setup the credentials for the call. So once we have the factory, we can do something like this:

channelFactory.Credentials.UserName.UserName = USERNAME;
channelFactory.Credentials.UserName.Password = PASSWORD;

You can get the username and password within a basic authentication website very easily by just looking at the auth header like so:

Request.ServerVariables("AUTH_USER")
Request.ServerVariables("AUTH_PASSWORD")

Ok, so we have the URI, we have the credentials, what's next?
First, let's make sure we are using the right version of WS-Trust. We stated in our URL we wanted to use 1.3 (trust/13/), so let's make sure that is what we are using.

factory.TrustVersion = TrustVersion.WSTrust13;

With that out of the way, let's make the actual call.

SecurityToken IdPSecurityToken = null
WSTrustChannel channel = null;
try
{
     //Build up our request for the Token
     RequestSecurityToken request = new RequestSecurityToken
            {
                RequestType = RequestTypes.Issue,
                AppliesTo = new EndpointReference(REALM),
                KeyType = KeyTypes.Bearer,
                TokenType = Constants.TokenTypes.SAML2
            };
        //The request is to ISSUE us a token, that APPLIES to the 
        //given realm, we want a BEARER token, and in our case 
        //SAML2 format is easiest.

        //Add any required claims you need for your application
        request.Claims.Add(ClaimTypes.Name);

        //Create the channel from the factory
        channel = (WSTrustChannel)factory.CreateChannel();

        //Actually send the Request to the IdP STS endpoint 
        //and get back the result
        IdPSecurityToken = channel.Issue(request);
    }
    finally
    {
        //Do some cleanup
        if (channel != null) { channel.Abort(); }
        factory.Abort();
    }

After that mess of code, you should have a IdP security token that was actively requested. Congrats! Next time we will dive into utilizing that token to actively federate with ACS, and get back a federation token.

Stay tuned!

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