Introduction

In my recent project, we wanted to use Thinktecture Identity Server for our authentication/authorization needs and while doing that exercise, I have come across many issues related to configuration and many findings. I just wanted to share my findings so that it will be useful to fellow developers.

The above diagram shows what I wanted to achieve and I think this is what a typical web application looks like.

What I Wanted to Achieve

Install & Configure Identity Server

Extend Identity Server to use our own data store to check the user credentials and get user claims

Implement SecurityToken Caching

Pass the token to our REST API Services

There are many articles you should definitely go through before reading this article. I have given all references at the bottom of the article. I will be discussing only the main points and solutions for each.

Identity Server Installation

I was able to install the identity server without any issues.There are many useful resources available and you should not have any problem. I had minor problems when using the self signed certificates as they are not trusted when used from other machines. So I wanted to go with OpenSSL so that I can set up a real world certificate authority and issue certificates as I wanted.

The other thing I wanted to achieve was to get the identity server check the user credentials against our own database rather than its own data store.

Certificates

Identity Server needs at least one SSL certificate for running as it needs to be hosted on HTTPS. It needs 2 more certificates for signing the security tokens and encryption but you can use the same certificate for all 3 requirements. So one certificate should be OK for now.

Extending Identity Server

Handling Complex Claims

Usually Claims are stored as simple key/value pair and both are of type "string" to keep it simple and reduce dependencies. But we wanted to store some extra information (like an object) along with Claim. I started with just serializing the complex object into a JSON string and storing that value as Claim value and I was able to deserialize it at the receiving end using JSON.NET. Even though this works, I found a good article where I found a more elegant approach. You can read about it here.

UI Web Application - Configuration

At the UI end, we did not want to redirect the users to the identity server site (which is what you normally see). Instead, we want to have a login screen of our own just like a regular forms authentication site and just use the STS in the background to check user credentials and get the claims associated.

In Global.asax (Application_Start), we need to add a messageHandler so that each HTTP call is intercepted before getting processed. This handler will check the HTTP Authorization header and decrypts it and populates the current user principal with all the claims.

You can also check for claims in code. Check the Get method in ValuesController. I have also provided some extension methods to help in checking the claims even easier.

When Your Session Cookie Becomes Too Big

Once your application becomes complex, so are the number of claims to handle. By default, all the claims are stored as part of the session cookie and browsers like Safari impose a restriction on the size of the cookie. So one fine day, when you add few more claims to the application, you will start getting serialization errors. That's because only partial cookie will be sent back to the server and server does not know what to do with it. So the solution for this problem is to create the security token in "Reference" mode. What it means is to store the token on the server and just store a reference session id as the cookie. See the image below. The cookie size is just few bytes:

SessionToken Caching

The Reference mode will work as long as you are on a single server instance scenario but it will not work when you have a web farm scenario because by default the cached tokens are stored in server memory. The solution to this problem is to cache the tokens in a custom data store. Again Thinktecture.IdentityModel is our friend here. All we need to do is to implement a simple interface and couple of lines of code added to Global.asax. I have provided implementations for caching the tokens in SQL Server/MongoDB/AppFabric. So you can choose whichever you want. In Global.asax, you need to add the below lines of code in Init method:

Common Errors

Here are some common errors you will come across while doing this exercise and solutions for the same. WIF10201: No valid key mapping found for securityToken: 'System.IdentityModel.Tokens.X509SecurityToken' and issuer: 'http://identityserver.v2.xyz.com/trust'.

Just make sure you have used the correct thumbprint for your certificate in issuerNameRegistry entry in web.config:

ID4243: Could not create a SecurityToken. A token was not found in the token cache and no cookie was found in the context.

I have seen this error mostly during development where I keep stopping/starting the development web server. What it is saying is that there is a session cookie found but nothing available on the server token cache corresponding to that cookie. You can just delete all the cookies for the domain and you should be good to go.

Points of Interest

I have kept all the common code in a separate project named IdenityServer.Demo.Common and I have written comments wherever I felt necessary. I have gathered all this information by going through many blogs and MSDN documentation and I have given references to most of them below. If I miss any one, that's totally unintentional and I am glad to add the reference if you let me know. Please let me know your comments and feedback.

I am in a mid-way of implementing what you have mentioned in your article. So far its going good.
Can you help me for how to renew a token? At the moment when token expires I am redirecting to login page. But for some special users I need to renew token. Is there a way to renew token?

In your usecase where you are forwarding the token from MVC to your API, i dont think you have to register your API as RP.it should work without registering as RP.
All you need at the API side is to establish the trust by installing the appropriate root certificate.
hope it helps
Azeet

SSO is what you get when you implement the identity server.But what we wanted is slightly different.For our application we dont want to redirect the user to Identity Server for signing in.
If you want SSO, you can just use the "Identity & Access Tool" to configure your application.but i think it configures to use SAML by default.so you may need to change the configuration to use JWT.
Hope it helps
Azeet

What you want to achieve is not possible in this scenario.If you want SSO then i think the only option is to redirect the user to STS and allow user to signin there so that STS will get a chance to set the authentication cookie at STS level.
I have tried all these options and it all works.I have gone through this current approach purely because of our own specific requirement and we dont have any other third party web sites.Another main reason for going thru this approach is that we have multiple login screens based on user role type.So instead of modifying the identity server source code ,we took the current approach.
Let me know if you need any help.
Azeet