Modern web technologies all favor newer token based authentication schemes, and most newer web applications never have any need to know a user's information or passwords.
This is fantastic if you are designing something new from the ground up. However, what if you have an existing web application that already uses a login system. How can you integrate modern authentication schemes with existing technologies without making sweeping changes? How can you utilize a SSO solution, even if the login begins from a legacy application? Well, I am glad to tell you there is hope, and a way, and I would love to tell you about how it can work.
Note: If you haven't already read it, my earlier post on SSO will help you get oriented with some of the terms and technologies discussed here. Check it out here.
First, let's set the stage and all of the players. A few months back, myself and another coworker Andy Steinmann (@andySteinmann) were stumped when tasked with building a new, cloud friendly multi-tenant application that could seamlessly integrate with existing legacy applications that utilized their own authentication mechanisms. In addition, there were some restrictions on what solution would be a good fit.
- It could not require an additional login. It should be seamless to transition to/from the existing legacy application.
- It must support both Web-based interactions from the client browser, as well as delegated API access from the legacy application.
- It must support a federated model where the logins of the various users come from the existing domain credentials that were on tenant premise.
- It must support cloud hosting and be multi-tenant. We are designing for the future, after all.
The existing application looked something like this, and was all on premise:
And to that we wanted to add a cloud hosted, SSO solution that could tightly integrate. I think you see the problem, and realize this is a bit of a tall order. However, it is far from unattainable, and despite most pre-canned authentication flows not meeting the needs, there was a way.
We had decided early on that the WSFederation protocol was the most attractive option for our particular use case, owing to ease of integration in a Microsoft-centric hosting environment, and easy tools for federation that fit our needs. However, while WSFederation provides both Active and Passive flows, the Microsoft Azure solutions offered only supported the Passive Flow. Before we go too far, lets make sure we define those:
Passive Flow - A federation/authentication flow in which the client is passive in interacting with the parties involved. The most common example is when the protocol utilizes browser redirects to complete the authentication flow. This means the application usually initiates the first redirect to a URL, and then handles the final response, but is not involved in any intermediate steps for authentication.
Active Flow - A federation/authentication flow in which the calling application is active participating in the flow between the parties. The most common example is a rich client that manually connects to each hop of the flow.
We had planned on using an Microsoft Service called Microsoft Windows Azure Active Directory Access Control (Yes, that is it's full name. And it is way too long. Lets just call it ACS from now on.) as our Federation Server. Oops, did it again, let's make sure everyone is on the same page with terms:
Federation Server - A Federation Server is a Secure Token Server (STS) that is trusted by the application, and in turn trusts one or more Identity Provider STS's. This is the server that provides the ultimate token that the application will use.
Secure Token Server (STS) - A STS is a server that provides cryptographic tokens in response to authentication requests. Authentication could be performed using passwords, certificates, tokens, or any of many different methods, and then the server will issue a token to the user if the authentication succeeds.
Identity Provider STS (IdP) - An IdP is a STS which is directly connected to the underlying identity store. For the Microsoft world, this would be the STS which accesses Active Directory to validate identities.
I apologize for the frequent break for terms, but I thought it was important to make sure there was no jargon, and this is a complicated topic.
Ok, so we planned on using ACS as our Federation server, and utilizing Microsoft's Azure Active Directory and a product called Active Directory Federation Services (ADFS) (A free windows service that acts as a STS. It is available to install as a component on Windows Server) as the IdP.
The products all worked great for the Passive flow out of the box, with very little configuration required. And there are plentiful tutorials and guides to make this work, so I won't spend long here. However, to meet our previously mentioned goals, we had to be able to initiate the SSO pro grammatically, something that is not possible passively, and requires the Active flow. However, as we quickly found out, Microsoft Azure Active Directory doesn't support a WSTrust endpoint (The underlying technology which WSFederation was built on) which is required for a WSFederation Active flow to work. After trying countless hours and talking with Microsoft technical contacts multiple times, we realized it was simply not going to work. So we gave up. Kinda.
We moved on to trying with the standalone ADFS, something that basically all of our clients would be utilizing anyway, and found much more promising leads. But what we did quickly run into is a complete brick wall in terms of documentation, tutorials, or guides. It was at that point I decide that I HAD to write this blog. Somebody needs to have guide! After many many frustrating hours, we were able to successfully design the system, and the resulting architecture is rather elegant.
So, here is what the final design looked like:
A bit complicated, so lets take it piece by piece.
We built the new, modern application to use the WSFederation Protocol, and to trust our one Federation provider, in this case ACS.
ACS then in turn trusted the ADFS instance of each tenant we onboard.
- At this point we have basic federation working, but not integrated into the existing application. So, let's keep building.
- We the enhanced the legacy application to submit the basic credentials of the logged in user to the ADFS instance on premise using the mixed mode username security WSTrust endpoint in ADFS (/adfs/services/trust/13/UsernameMixed is usually the url) and retrieve an IdP token.
- The IdP token is then submitted by the legacy application to the ACS instance to get back a federated token that the application will trust
- At this point we have a valid token, from a trusted STS (The federation server) and simply need to use it.
- To allow the client browser to use the token we craft a post to the login url of the modern application, posting the federation token just like the passive flow would do naturally.The normal Owin middleware takes over and processes the login as normal.
- For API use, we Base64 encode the token, place it in the HTTP auth headers, and access the API as usual. On the cloud application we simply unpack the token from the header, decode and validate it, and use it as we would any other security token.
Whew, it took longer than expected to explain the overview and we are out of time. Stay tuned until next time where I will dive into code snippets, and the details of how everything comes together. I would love to hear feedback to see if this is an interesting topic, and if the overview makes sense.