Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API

Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API

Last Updated on November 13, 2022 by Aram

In this tutorial we will learn how to Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API. We will build a simple, secure and reliable RESTful API project to properly authenticate users and authorize them to perform operations on the APIs.

Note: I have updated this tutorial and the GitHub source code to the latest version of .NET, which is .NET 7.

We will use the latest and greatest version of Visual Studio 2022 – Community Edition to build our ASP.NET Core Web API using the latest and fastest .NET: which is .NET 7 and C# 11.

The good news is that VS 2022 comes bundled with the latest version of .NET and C#, so you don’t have to worry about searching and installing any of them. So, if you haven’t got the VS 2022 already, make sure you download and install Visual Studio 2022 so we can get started with our tutorial to Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API.

In a previous tutorial we learned how to Secure ASP.NET Core Web API using JWT Authentication but that was only using access tokens. Now this is a new tutorial built from the ground up explaining everything about JWT Authentication while using both access and refresh tokens.

Access Tokens vs Refresh Tokens

We use an access token to grant a user the proper authorization to access some resources on the server when it is provided in the Authorization header. An access token is usually short-timed and signed, as for a JWT Token, this will include the signature, claims, headers.

On the other hand, a refresh token is usually a reference that can be used only to refresh the access token. Such token is usually persisted in a backend storage and can be used to revoke access for users who, for example, who are longer eligible to access these resources or in the case of a malicious user who stole an access token.

In such cases, you can just remove the refresh token for these devices, so once their access token is expired they won’t be able to renew (refresh) it using the revoked refresh token because their once-valid refresh token is no longer valid and they will no longer be able to access your resources. Therefore, the user will be signed out in the app or web so they will have to re login and go through the usual login process again.

Now enough with the rigid texts, let’s jump right into building our APIs that will implement the JWT Authentication using both access and refresh tokens using ASP.NET Core Web API in NET.

Starting the tutorial

We will build a simple tasks management System, that allows the authenticated user to manage their own tasks. This is just a simple and basic representation of the tasks management system.

Feel free to fork it from GitHub and build on it for your personal projects.

Database Preparation

For most of my tutorials, I am using SQL Server Express to create the database and the tables needed. So make sure you download and install the latest version of SQL Server Management Studio and SQL Server Express.

Once both are installed, open SQL Server Management Studio and connect to your local machine where the SQL Server Express is installed:

sql server - connect

From the object explorer, right-click on databases and choose “Create new database” give it a name like TasksDb:

sql server - create db

Then Run the below commands to create the table and populate it with the data required for this tutorial:

Project Creation

Open Visual Studio 2022, and create a new project of type ASP.NET Core Web API:

vs2022 - create project

Give it a name like TasksApi:

Then choose .NET 7.0 and create the project:

Once VS completes the initialization of the project, press F5 to do an initial run for the template project to make sure that it works fine.

Now, let’s remove some unneeded classes from the template project. From solution explorer, delete WeatherForecastController and WeatherForecast files.

vs2022 - project template

Entity Framework Core and DbContext

Let’s add Nuget package for EF Core and EF Core Sql:

As you can notice, now we have EF Core 7, there are many great improvements happened from EF Core 6 to 7, check the release notes of EF Core 7 to learn more.

Entities

Now let’s create the needed entities that will bind to the Database tables through the EF Core DbContext class.

We will create 3 entities that will map to the Tasks database.

RefreshToken

Task

User

DbContext

Now let’s add the TasksDbContext that will inherit from the DbContext class of the EF Core:

And in your program.cs file, add the below just before the builder.build() call:

Then in your appsettings.json , make sure to include the connection string for the database:

Breaking Change in EF Core 7

Important note here about the TrustServerCertifiicate=True, this is only an unsecure workaround just for the sake of testing localhost.

Since there has been a breaking change with the release of EF Core 7, which is related to the connection encryption with SQL Server. This means that now by default any connection to SQL Server database is encrypted, through the Encrypt=True portion of the connection string in case it is not provided otherwise, and there must be a trusted certificate installed on the machine hosting the SQL Server. You can read more about the breaking change in EF Core 7.

So, just to show you what will happen when we try to connect to SQL server in .NET 7 using .NET Core 7, using the default connection string without installing a trusted certificate, I will fast forward this tutorial and try to run the application on Postman via https://localhost, I will get the below error message:

Microsoft.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)
---> System.ComponentModel.Win32Exception (0x80090325): The certificate chain was issued by an authority that is not trusted.

So only for localhost testing purposes, as a workaround, we can bypass this through adding TrustServerCertificate=True.

You can instead install a trusted certificate and then you can remove the TrustServerCertificate=True from the connection string.

EF Core Power Tools

Note: I have used the wonderful extension EF Core Power Tools which in a magical way it can translate a whole database structure and relationships into neat and proper DbContext entities and configurations.

You can install it from Extensions tab -> Manage Extensions of your Visual Studio 2022

vs2022 - ef core power tools

If you are following the design-first model for building your database first and then your EF Core mapping, I would highly recommend that you use this blazing fast and reliable tool to perform such operation, which will therefore boost your productivity and reduce the number of errors that might be introduced from manually creating the entities and configurations.

PasswordHashHelper

In order to save passwords on the database, we need to use secure hash HMAC 256 and salt from a secure random bytes of 256-bit sized, so we can protect the valuable users’ passwords from those nasty lurking thieves!

We will also use these helper methods to save the refresh tokens on the database in a hashed format alongside their associated salts.

Implementing the JWT Authentication

Let’s add the needed JWT Bearer Package, which is also available in .NET 7:

Token Helper to build both Access Tokens and Refresh Tokens

Now let’s add the TokenHelper, which will include 2 methods to generate JWT-based access tokens and the other to generate a 32-byte based refresh tokens:

Now let’s make sure to add the needed authentication and authorization middleware to the pipeline in program.cs file:

Add the below before the builder.build method:

And then before the app.run method, make sure you the app will be using both middleware to authenticate and authorize your users:

Requests and Responses

It is always advised that you accept and return structured objects instead of separated data, this is why we will prepare some Request and Response class that we will use them throughout our API:

Let’s add the below requests classes:

LoginRequest

RefreshTokenRequest

SignupRequest

TaskRequest

Now let’s add the responses classes, these will be used to return the structured responses for the UI client calling the API:

BaseResponse

This will be used a base class so other response classes can inherit from and extend their properties:

DeleteTaskResponse

GetTasksResponse

LogoutResponse

SaveTaskResponse

SignupResponse

TaskResponse

TokenResponse

ValidateRefreshTokenResponse

Interfaces

We will define 3 interfaces that will be implemented within the services. The interfaces are the abstractions that the Controllers would need to use to be able to process the related business logic and database calls, each interface would be implemented within a service which would be injected at runtime.

This is a very useful strategy (or design pattern) to make your API loosely coupled and easily testable.

ITokenService

IUserService

ITasksInterface

Services

Services act as the intermediate layer between your Controllers and your DbContext, it also includes any business related logic that the Controller should not bother about. Services implement the interfaces.

We will add 3 services:

TokenService

This will includes methods to generate tokens, validate and remove refresh tokens:

UserService

This will include methods related to login, logout and signup:

TaskService

This includes the methods for adding, removing and getting tasks:

Now, once you add those Interfaces and Tasks, let’s make sure that we configure them within the project’s builder pipeline:

Controllers

Now it is the last part of the API, which is to build the endpoints that will be used by the users to access the backend resources:

First, we will make a new Controller that will inherit for the ControllerBase and inside it we will have a small method and property to retrieve the logged in UserId, whenever the access token is provided, from the JWT-based access token claims:

So let’s add an API Controller, like the below:

BaseApiController

Now we can create our controllers that will inherit from this BaseApiController.

UsersController

Let’s start with the UsersController, it will include 4 methods: login, logout, signup and refresh the access token:

Note above that only the logout endpoint has the Authorize decoration, this is because we know that the user will be able to logout as long as he is logged in, which means he has a valid access token and refresh token.

TasksController

This includes all the endpoints that will allow the user to perform tasks-related operations, like getting all user’s tasks, saving and deleting the task for that user.

Note that the [Authorize] attribute is decorating the whole Controller since all these operations require an authenticated user with a valid access token.

Now we are completed with the development part. Press F5 and check the browser is showing Swagger UI for your APIs

vs2022 - run - swagger

Testing on Postman

Now comes the QA part of testing our whole work to make sure everything is working fine and as per the requirements.

Of course, make sure you have the latest version of Postman installed and opened.

Let’s create a new collection and name it , Tasks Api.

First thing we need to test the login endpoint since we have some test user already inserted in the database (included in the script part at the beginning of the tutorial)

postman - login - success

Let’s try to invalidate the email and see the result:

postman - login - fail

Now let’s test the signup method:

postman - signup

Take a look at the database User Table:

Notice the 3rd record, the password was never saved in plain format, and the random salt was associated with it.

The access token usually would have a short duration, 10 or 15 minutes long, and once this is expired you have to silently refresh the access token using the refresh token, which is much longer in duration, like 10 days or 3 weeks for example, and these tokens are sliding in time, so whenever you want to refresh and access token you can just use the below endpoint to generate new pair of tokens.

refresh_token endpoint

Now let’s test the refresh token endpoint. You will need this endpoint to refresh the access token for the user after it becomes expired through any of the authorized API calls, such as the below:

postman - get tasks - fail

You will get 401 response, because the access token is no longer valid and you have to request for a new access token using the refresh token that you had from the first login.

Let’s try to refresh token:

postman - refresh token - success

If we try to refresh the same token used before, it won’t work, simply because the refresh token triggers generating both new access token and new refresh token, so the previous refresh token would be invalidated (removed from the RefreshToken table on the database).

postman - refresh token - fail

Now let’s logout the user

postman - logout

Malicious or already logged out user

Now let’s try this scenario, a valid user logs out of the system, but incidentally a malicious user has already obtained the refresh token for that user (in some way), and tries to refresh that user’s token so that he can gain unauthorized access, our APIs would guard our valid user by returning the below response for the malicious user.

This way the malicious user cannot gain access to the valid user’s data.

postman - refresh token fail

Let’s now do Get Tasks for the user:

postman - get tasks

And let’s try adding a new task

postman - add task

and now let’s delete a task

postman delete task

Summary

Today we have learned how to build a small simple tasks management system starting from the database using SQL Server Express, connecting it with ASP.NET Core Web API in .NET 7 and C# 11 within Visual Studio 2022.

We mapped the database with EF Core 7 (with the generous help of the great EF Core Power Tools) and then using the JWTBearer Nuget package we managed to setup and implement the JWT-based authentication on the API project alongside applying the refresh tokens to make it even more practical for end users to get authenticated and authorized for the APIs without having to re-do the login process every 10 or 15 minutes.

If you think this tutorial is useful, please feel free to share it within your online network and with your colleagues. And don’t forget to subscribe to my blog to be notified once a new tutorial is posted.

Please let me know your thoughts or inquiries down below in the comments section.

References

You can find the source code, updated to .NET 7, for this tutorial in my GitHub account.

I have another tutorial that explains a little more about JWT, you can take a look sometime Secure ASP.NET Core Web API using JWT Authentication.

Feel free to check my other tutorials as well, I will make sure to update them to .NET 7 the soonest:

Also for more details about improving your Web API Security, you can check my article: Boost your Web API Security with These Tips

If you are new to ASP.NET Core Web API, feel free to read my post: A Quick Guide to Learn ASP.NET Core Web API

Bonus

Please enjoy listening to this beautiful piano sonata.

Have a great day or night wherever you are, have fun listening and coding! and most importantly .. Please Stay Safe!

Mozart – Piano Sonata No. 8 in A minor, K. 310 (1st Movement)

11 Comments on “Apply JWT Access Tokens and Refresh Tokens in ASP.NET Core Web API”

  1. I had an issue with setting the “Expires” time but adjusted it to be Expires = DateTime.UtcNow.AddMinutes(15) with the UtcNow and it worked for me. Great job mate!

  2. Excellent content. It really helped me connect the gaps in the material I am working on.
    I noticed an inconsistency in your code. Your SQL commands set the length of TokenSalt to 50, but your generated TasksDbContext has the max length as 1000. My generated TasksDbContext has the same length as was set in the db.
    I am not very experienced with Authentication. If 50 is too short I am sure I will find out. Just an observation.

  3. Excellent post! One of the best tutorials I have read.

    I have 2 questions:

    #1
    I have a question about the ‘malicious user scenario’.

    After a user logs out, will the malicious user be able to use the access token to perform authenticated calls? The access token has not expired and was not invalidated.

    After the access token expires, the malicious user should not be able to perform authenticated actions… However, in the time between the expiration of the access token, and the time that the user log’s out — can the malicious user perform authenticated actions?

    #2 – Why did you create a new database table called ‘RefreshToken’ instead of use AspNetUserTokens?

    Thanks!

    1. Thank you for your nice words. I am glad that you find this tutorial helpful.

      Regarding your questions:

      #1 Access tokens are usually meant to be short time-lived tokens that get invalidated once the lifetime of the token ends, and they are generated for users for a time-limited usage and they are not linked or stored with refresh token, so usually there is no way to specifically invalidate a valid access or JWT token. So that malicious user ‘will be’ able to access your APIs with the previous access token since it is still valid, but that should be only for a very short duration. This is why you should always keep your access tokens very short lived (usually 15 or 30 mins) and silently refresh them for a better user experience, and then if you think your tokens were compromised, then you can just remove the refresh token from your database, this will result in any user having your old refresh token won’t be able to generate new access token / refresh token until they get signed out from the application.

      # The AspNetUserTokens is used by the ASP.NET Identity to store token information of your users who log in through the external identity providers like Google, Twitter, Github … etc. So usually the usage of this table comes bundled with the usage of ASP.NET Identity.
      On the other hand, this tutorial helps in building a custom implementation for JWT Access tokens and refresh tokens (using the JWT , where your APIs also act as the identity provider, in this way we can control our own implementation for identity by storing and managing those refresh tokens for your users.

  4. Great article, thank you.

    Sign-up and Sign-in requests work as expected. Subsequent access_token generation works and allows accessing protected data.

    I am a bit confused about the sign-out and refresh_token requests. First, I am not sure about the order. Does it matter? I’ve tried both ways expecting that protected API endpoints would not longer be accessible. But after I refresh_token and sign-out, the previously generated access_token still grants users access to protected endpoints. What am I doing wrong?

    Regards,
    Tolga

    1. Hello, Really sorry for the very late reply, thank you for your message. Access Tokens are not revokable, they can only be invalidated when their lifetime is over, and they are not tightly coupled with their refresh tokens, so you can have as many refresh tokens as possible and still the first access token will remain valid until it expires. This is why the recommendations are to use very short lived access tokens ( 15 or 30 minutes ) and have refresh token setup with database persistence so you can control the devices/channels that are connected and revoke ( delete ) the refresh token for any suspicious device activity. I hope that answered your question. Sorry again.

Leave a Reply