SSO with Windows Identity Foundation: Part II - Claims
Previously we described the general problem of what we're trying to solve, and today we're going to delve into claims.
Claims Based Authentication
In part I of the tutorial we described a claim as a key value pair of information related to some user. Let's be clear: since .NET 4.5, all identities are claims based. That means all identity objects consist of a list of claims. Claims became a first class citizen, and all of the core features will be built on claims when there is user identity involved. For example FormsIdentity is based on ClaimsIdentity. This applies to all types of Identity in 4.5.
What all this means is very important - you can implement authentication in your application with any technology (Forms, Membership, etc) that you can afford, and you can make it Claims Aware later with little to no refactoring. The requirement might be that your customer needs single sign on. The previous authentication code will work, because all identity types are interchangeable. If you're using IsAuthenticated and IsInRole and have your identity be of type FormsIdentity, they will continue to work even when you introduce an IdP into your authentication chain.
You don't have to make a big deal of what authentication scenario you go with for your minimum viable product, as you can safely expand that solution to work with an IdP later on and will not have to change code to make it work. Everything being based on claims allows your application to evolve according to your customers' requirements without the need to redo your authentication logic.
Prove it
I will - we're going to start building our application by making a normal ASP.NET MVC 5 application and making it work with Forms Authentication. Then on a separate development move we're going to implement an IdP after which we make our Forms Authentication application claims aware.
I made a new ASP.NET MVC 5 application, stripped some irrelevant things from it, and made it work with Forms Authentication.
You can view this code over at GitHub.
So what exactly did we start with?
Let's see the relevant stuff:
As that code proves (and you can also verify by running the code), the forms identity is in fact claims based by default, and we'll continue building on this example.
Let's see what claims does the FormsIdentity have.
As we can see, it only contains one claim of type "name" which has the value "lari".
This makes sense, as Forms Authentication only puts one thing in the authentication cookie, and that is your username.
As this demonstrates, our good old Forms Authentication is built on claims, and the point is this: If we now implement an external identity provider and plug this application we just made to use that IdP, we will be able to use the exact same Authorize attributes, IsAuthenticated calls and IsInRole calls as we are using now, and the application will just work. The only change we'll have to implement is changing the Login code to work with the IdP.
What can you model with claims?
Here's a list of typical claims over at MSDN.
My personal experience is that the best thing to do is to save external system Ids into claims. Let's say that your user is authenticated against an external user store that is called ContosoUsers. You'd associate a user with this store in the following way (pseudocode):
Common problems
Note that the job of the STS is to build the identity object and send it to the RP. The identity object consists of a list of claims. The above code would be run on the STS.
The fact that the authentication concern is handled externally comes with (at least) these two speedbumps.
- To build the identity, you might need information that only the relying party has access to. For example, only the server that is running the front end has access to some external system that you need to include in the identity for the whole system to work. To achieve this there is technology built into WIF called Claims Tranformation.
- The relying party might need a lot of data that only the IdP has access to. For example, in some architecture scenarios your IdP will be installed on a different server, and only that server has a firewall open to the corporate Active Directory. Your relying party might need a lot of information from that AD, but can only get it as claims from the IdP. This will bloat your cookies and kill your client performance. This can be fixed with built in WIF technology which involves configuring the WIF auth cookies to work in reference mode. This means that the bulk of the cookie data is cached on server, and the cookie only contains a reference id.
Hopefully I'll be able to cover these topics in detail at a later date.
Next time we'll start implementing our own identity provider, and only implement it as far as to get the WS federation messages to move over the wire.