Refresh JWT Token with ASP.NET (Core) (C#)
I’ll be going straight to the point on how to implement a Refresh token for ASP.NET (Core) so this story assumes that you have already implemented JWT Tokens. There are a lot of articles on that one 😄
What are Refresh Tokens?
In a nutshell, you can think of refresh tokens as:
A token that can be used to receive a new access token (in our case, JWT Tokens) without having to re-send credentials.
So how do we implement one?
Before that, please take note that the following changes must be done to your existing login endpoint (for me, it’s under /api/Auth/login
) :
- Modify your return object in your login endpoint so that you return the following:
{
"accessToken": "<JWT Token>",
"accessTokenExpiration": "<A value that tells up to when is the access token valid."
"refreshToken": "<Refresh Token>"
}
- “accessToken” — This is basically your JWT token.
- “accessTokenExpiration” — This is optional. But this represents a value that tells your client up to when is the access token valid. It’s up to you if you want to return a double to indicate the minutes. I usually use DateTimeOffset for this one.
- “refreshToken” — This is where you will place the Refresh token that the client can use in order to receive a new JWT Token.
2. Modify how you generate your JWT Token by ensuring that an identifier for the user is included in your JWT Token via Claims or via or Subject:
Note that you need to add a Claim that you can use to easily identify the user based on the JWT Token. I usually use Claim ClaimTypes.Name
for this one.
3. In your login workflow, make sure that the “Refresh Token Flow during Login” and “Using a Refresh Token”(see below).
Refresh Token Flow during Login
This flow can be placed before or after generating a JWT Token.
- Generate a random token:
The idea behind the value of the token is that it has to be as random as possible and is something that can’t be easily guessed.
You can use your own implementation on generating the random value but for example above I used System.Security.Cryptography.RandomNumberGenerator
.
Some suggests that you can use GUID for this one but others say that GUID has a pattern. In the end, it’s up to you to decide what to use.
2. Store your Refresh Token along with it’s expiration in your database / repository.
This is a very important step. Remember that our refresh tokens are really random and is really hard to guess?
Well, we have to find a way to validate if the given refresh token to us valid or not. So how do we do that? Store your refresh tokens somewhere.
Here’s an overview of the whole flow:
You may be wondering why am I using a “list of Refresh Tokens”.
The reason for that one is that if your app allows for a user to be logged in into multiple devices, then each of those device will have its own refresh token.
Using a Refresh Token
Now that we have given a refresh token to the client, the next question would be how do we use them right?
- Create a new endpoint that allows AnonymousAccess:
We need this to be anonymous since we might be receiving an invalid JWT token but a valid refresh token.
The response is similar to the one returned by the login endpoint.
2. Retrieve the ID of the user from the JWT Token (this would also tell us if the JWT Token is valid or not)
3. Now that we have retrieve the id of the user, we can now retrieve the stored refresh tokens from that client and verify if the refresh token is valid or not:
4. Once you have validated that the refresh token is valid, you can now generate a new JWT Token with a new expiration and a new refresh token as well and return them to the client. 😄
Things to consider:
- When using a refresh token, it is best to delete them from the database so that it can’t be used again. I usually delete them immediately once the refresh token has been used.
- There are times where the refresh tokens are not used and is already expired. I have a
HostedService
that periodically runs to clean them up. - If you only want your user to be active on just one device, there’s no need to store multiple Refresh tokens in your repository.