I updated this article on May 3rd with a better way to deal with refresh tokens and to accomodate for an update of the JWT package.
This week I was improving the backend of an app of our company. It used basic authentication to authenticate users against the database. Because we were implementing multifactor authentication, it needed to be improved. At the same time the server response wasn’t that fast.
I figured JSON Web Tokens would solve that problem, while simultaneously solve a minor security concern with basic authentication. While all traffic is done over SSL, the password is still sent over the network, risking a MITM-attack. Then I searched for a way to implement this securely, and while I found some helpful guides, they didn’t satisfy everything.
For the people who don’t know, JSON Web Tokens are being used as tokens to securely transfer data between 2 parties. This data is actually not encrypted, so don’t put sensitive information in your JWT! This token is signed by the server, so others can’t mutate this data.
There is one major caveat: if this token somehow ended up in the hands of an attacker, this attacker has access to everything the user has. The token can only be invalidated by changing the secret, which will invalidate all tokens. That’s not something we want to do on a regular basis. So to mitigate this problem, tokens need to be short-lived. You can do this by setting an “exp”-claim on the token. It should only live for 5 to 15 minutes. After that, they need to be renewed.
So how are we going to do that in C#? First of all, there are a few packages to implement JWT in our app. There is System.IdentityModel.Tokens.Jwt from Microsoft itself, but I’m using JWT from Alexander Batishchev. It has a fluent builder API which I love.
First I created a TokenManager. From there I will create static methods to generate and verify the tokens.
Let’s create the Generate-method. This method will return the token, which is a string. I uses the JwtBuilder-method. We encode the token with SHA256, so we set that first. After that we set the secret. Then the fun can begin! We can add claims to our token, so we can safely send data back and forth. We start with setting the expiration, because that’s needed for security. Lastly we call the Encode-method to generate and return the token.
So what can we add to the token? I needed their username and role to check if they are permitted to access the API resources. So I added that to the token.
Now we need to verify these claims. It’s actually quite simple with the JWT package, but with Dotnet Core there is another way that’s easier to implement. First with the JWT package:
It automatically checks the expirationdate if we add MustVerifySignature(). Lastly we need to decode it. We can decode it to an IDictionary, so we can access the claims like “claims[“username”]”.
In Dotnet core can use a snippet in Startup.cs. You don’t need the VerifyToken function above, but you can add the following to the Startup.cs:
It uses the same secret as the JwtBuilder.
Now we can generate and verify JWT tokens. Easy, right? We just need a way to renew these tokens if they expire. Our users won’t like to log in every few minutes.
To illustrate the danger of not setting an expiration date on a token, I actually had this problem at a major music service. Someone had broken into my account and was using it. So I changed the password and thought it would be over, but the attacker was still using my account. Strange songs appeared in my last played list and the automatically created playlists were utterly messed up. So I talked to their support and the only thing they could do, was create a new account and transfer my billing information to that new account. Then I could delete my old account. It’s sufficient to say I went to their competitor instead.
We need refresh tokens. How are we going to create and verify them? And how does the client know it should get a new access token? The idea is as follows:
As the user logs in with their username and password, the API generates two tokens for the client. The access token to get a few minutes access to the resources and the refresh token to generate a new access token. The refresh token is saved in the database.
As the client tries to get a new access token, the API should check if the refresh token is correct and if it matches a token in the database. If not, it should decline the request. If it exists, the API should return a new access token and a new refresh token. Then the old token should be deleted from the database. By doing that you’re making sure a refresh token can only be used once.
I use MongoDB so I added a “List<string> Refreshtokens” to the properties of my user. It’s a list, because there will be one refresh token per device and my users will want to log in with multiple devices without being logged out.
The method to create a new refresh token will contain the username and a randomly generated key. It looks like this:
I set it to expire in 4 hours, to be extra safe. If the user doesn’t refresh in 4 hours the refresh token will be invalid and they will need to log in again. This ensures safety.
So, how will we check this token? If you’re using the JWT package, you can use the same function as checking the access token. If you’re creating an API with Dotnet Core I would go a slightly different route.
First, I would add the .Audience() to the builders at the GenerateAccessToken and GenerateRefreshToken methods. Use the intended usage as value. Then I would do the following in the Startup.cs:
As you can see I’m adding two Authentication Schemes to my API, both of them JWT Bearers. The only thing different between them is the audience. The default Authentication Scheme will decline tokens with the “refresh” audience, the other one will decline the “access” audience. You can use this on your controllers like this:
So you can get the value of your claims like explained above. Now you can check them against your database to decide you’re going to give them a new access token or not.
So there you have it. We created JSON Web Tokens which will expire after a few minutes. Then we created a way to use refresh tokens, which will expire in a few hours. Even if an attacker got a hold on the refresh token, by simply deleting it from the database he will not be able to use it. Create an action to delete all refresh tokens of this specific user and he will not be forced to create a new account and transfer all his data.
I hope this helps in creating and improving your own secure applications. If you have any questions, please ask them in the comments. I will return to you as soon as I can.
You can find a full working version here: https://github.com/mauritsderuiter95/JwtExample