Building an Identity Server for authentication and authorization is quite straightforward. You can easily find a tutorial for this then install the required packages, copy-paste-modify some line of codes and congratulations! You’re done.
Everything is working well as you expected: authenticate user, issue JWT token then authorize token…. till a day your application move to Microservice architecture and it means that your IdentityServer will need to run on multiple instances. Is it easy? Let see how we can deal with this!
That is my story that I’m going to tell you…
Currently, I’m working on a project that applies Microservice architecture using Docker Swarm (1 master 3 workers). Using Docker Swarm, I can deploy multiple services, on multiple nodes. That way one application is built with multiple different services can have replicas on many nodes within Swarm network. It gives me some amount of scalability and responsiveness.
Basically, my application similar to other applications.
· A bunch of small, stateless web APIs (using .NET Core 2.2)
· Stateless authentication service (IdentityServer4)
I run 3 replicas for each service. So, what is my problem here?
After implementing IdentityServer, running Unit Test — all green and testing locally I was happy to deploy. The service worked smoothly as I expected. It’s time to deploy and back home!
The day after, as a routine I grab a cup of coffee then open JIRA board. A critical bug regarding IdentityServer was assigned to me. Here is the bug title: “Authorization token — Return 401 error when using valid token” and steps to reproduce:
1. Get token from IdentityServer successfully
2. Submit a request to Purchase service with token above
3. The server returns HTTP 401 — Unauthorized
4. Call to Purchase service again with same token above
5. The server returns HTTP 200 — OK
It’s weird! I tested very carefully on my local machine before deploy. So, what happened?
After a few hours of investigation, I realized that IdentityServer runs with only one instance on my local machine while it runs with multiple instances on the production environment. See pictures below:
The obvious point of failure is that I get JWT token with instance #1 while Purchase API use instance #2. So, what is the culprit here?
Signing Key, I catch you!
By default, Identity Server uses Temporary Signing Certificate to sign the JWT tokens via this method: .AddDeveloperSigningCredential()
Once Identity Server starts/restarts, a temporary key is created and make all the keys created before invalid. So, there are 3 temporary keys were created in my case.
Alright, what is the solution? Pretty simple, make all instances using the same key (certificate).
Let’s do it!
Step 1: Creating the Self-Signed Cert (using OpenSSL)
NOTE: I would not recommend using Self-Signed Cert for Production.
1. Download and install OpenSSL from https://www.openssl.org/source/
2. Add bin folder to the system path
3. Create the certificate and private key
Once OpenSSL is installed, we can use it to create the certificate. Let’s open Powershell or Cmd and run following command:
Output:
You can edit the certificate name (i.e. IdentityServerCert) or subject if you like, and also feel free to change expiry days (it is set to 10 years).
4. Convert certificate and key to pfx format
You’ll be asked to enter an export password. Whenever you want to use pfx file you will need this password, so keep it handy.
Step 2: Loading Certificate
I mentioned before, I used ASP.NET Core 2.2 for my project. So I wrote the following code in ConfigureServices method (StartUp.cs).
There are several ways to load certificate and it depends on how you store certificate.
- Load certificate from the registry
2. Load certificate directly
3. Load certificate from Azure Key Vault (I would like to recommend this as I’m using this way to store and load certificate)
I will assume that you already know how to import a certificate to Azure Key Vault. If you don’t know, you can find all you need in the article: https://docs.microsoft.com/en-us/azure/key-vault/
Final Step
Replace “AddDeveloperSigningCredential” method by “AddSigningCredential” and use our certificate.
Recap
So I did it, it still works well until now. It wasn’t really complicated, was it? With this article, I hope I can help you to save time for finding out the problem with IdentityServer as well as how to fix it.