API with NestJS #13. Implementing refresh tokens using JWT

JavaScript NestJS TypeScript

This entry is part 13 of 51 in the API with NestJS

In the third part of this series, we’ve implemented authentication with JWT, Passport, cookies, and bcrypt. It leaves quite a bit of room for improvement. In this article, we look into refresh tokens.

You can find all of the code from this series in this repository

Why do we need refresh tokens?

So far, we’ve implemented JWT access tokens. They have a specific expiration time that should be short. If someone steals it from our user, the token is usable just until it expires.

After the user logs in successfully, we send back the access token. Let’s say that it has an expiry of 15 minutes. During this period, it can be used by the user to authenticate while making various requests to our API.

After the expiry time passes, the user needs to log in by again providing the username and password. This does not create the best user experience, unfortunately. On the other hand, increasing the expiry time of our access token might make our API less secure.

The solution to the above issue might be refresh tokens. The basic idea is that on a successful log-in, we create two separate JWT tokens. One is an access token that is valid for 15 minutes. The other one is a refresh token that has an expiry of a week, for example.

How refresh tokens work

The user saves both of the tokens in cookies but uses just the access token to authenticate while making requests. It works for 15 minutes without issues. Once the API states that the access token expires, the user needs to perform a refresh.

The crucial thing about storing tokens in cookies is that they should use the httpOnly flag. For more information, check out Cookies: explaining document.cookie and the Set-Cookie header

To refresh the token, the user needs to call a separate endpoint, called  . This time, the refresh token is taken from the cookies and sent to the API. If it is valid and not expired, the user receives the new access token. Thanks to that, there is no need to provide the username and password again.

Addressing some of the potential issues

Unfortunately, we need to consider the situation in which the refresh token is stolen. It is quite a sensitive piece of data, almost as much as the password.

We need to deal with the above issue in some way. The most straightforward way of doing so is changing the JWT secret once we know about the data leak. Doing that would render all of our refresh tokens invalid, and therefore, unusable.

We might not want to log out every user from our application, though. Assuming we know the affected user, we would like to make just one refresh token invalid. JWT is in its core stateless, though.

One of the solutions that we might stumble upon while browsing the web is a blacklist. Every time someone uses a refresh token, we check if it is in the blacklist first. Unfortunately, this does not seem like a solution that would have good enough performance. Checking the blacklist upon every token refresh and keeping it up-to-date might be a demanding task.

An alternative is saving the current refresh token in the database upon logging in. When someone performs a refresh, we check if the token kept in the database matches the provided one. If it is not the case, we reject the request. Thanks to doing the above, we can easily make the token of a particular person invalid by removing it from the database.

Logging out

So far, when the user logged out, we’ve just removed the JWT token from cookies. While this might be a viable solution for tokens with a short expiry time, it creates some issues with refresh tokens. Even though we removed the refresh token from the browser, it is still valid for a long time.

We can address the above issue by removing the refresh token from the database once the user logs out. If someone tries to use the refresh token before it expires, it is not possible anymore.

Preventing logging in on multiple devices

Let’s assume that we provide services that require a monthly payment. Allowing many people to use the same account at the same time might have a negative impact on our business.

Saving the refresh token upon logging in can help us deal with the above issue too. If someone uses the same user credentials successfully, it overwrites the refresh token stored in the database. Thanks to doing that, the previous person is not able to use the old refresh token anymore.

A potential database leak

We’ve mentioned that the refresh token is sensitive data. If it leaks out, the attacker can easily impersonate our user.

We have a similar case with the passwords. This is why we keep hashes of the passwords instead of just plain text. We can improve our refresh token solution similarly.

If we hash our refresh tokens before saving them in the database, we prevent the attacker from using them even if our database is leaked.

Implementation in NestJS

The first thing to do is to add new environment variables. We want the secret used for generating refresh token to be different.

Now, let’s add the column in our User entity so that we can save the refresh tokens in the database.

We also need to create a function for creating a method for creating a cookie with the refresh token.

The possibility to provide the secret while calling the   method has been added in the    version of 

An improvement to the above would be to fiddle with the   parameter of the refresh token cookie so that the browser does not send it with every request.

We also need to create a method for saving the hash of the current refresh token.

Let’s make sure that we send both cookies when logging in.

Creating an endpoint that uses the refresh token

Now we can start handling the incoming refresh token. For starters, let’s deal with checking if the token from cookies matches the one in the database. To do that, we need to create a new method in the  .

Now, we need to create a new strategy for Passport. Please note that we use the   parameter so that we can access the cookies in our   method.

To use the above strategy, we also need to create a new guard.

Now, the last thing to do is to create the   endpoint.

Improving the log-out flow

The last thing is to modify the log-out flow. First, let’s create a method that generates cookies to clear both the access token and the refresh token.

Now, we need to create a piece of code that removes the refresh token from the database.

Let’s add all of the above to our   endpoint.


By doing all of the above, we now have a fully functional refresh token flow. We also addressed a few issues that you might face when implementing authentication, such as potential database leaks and unwanted logging in on multiple devices. There is still a place for further improvements, such as making fewer queries to the database when authenticating with the access token.

What are your thoughts on the solutions implemented in this article?

Series Navigation<< API with NestJS #12. Introduction to ElasticsearchAPI with NestJS #14. Improving performance of our Postgres database with indexes >>
Notify of
Newest Most Voted
Inline Feedbacks
View all comments
1 year ago

The maximum input length is 72 bytes (note that UTF8 encoded characters use up to 4 bytes) and the length of generated hashes is 60 characters.

I get this infomation from https://github.com/dcodeIO/bcrypt.js#security-considerations , so bcrypt compare the tokens always return true when the first 72 letters are the same.
Thanks for your work.

11 months ago
Reply to  gls

> Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 characters

A workaround is to hash the string first using SHA-256, and then use bcrypt.

Last edited 11 months ago by Oss
1 year ago

Thank for sharing !

11 months ago

If I understand this right, this is a cookie authentication and cookie authorization based approach. It’s most suited for browser-based web apps as cookie is sent on every request. Client doesn’t need to worry about it. While a lot of new web apps send the access token to the client in the form of a json blob which will leave the client to manage the access token and send back to server as “authorization bearer” header which is based on the reason that this way will prevent CSRF attack since no cookie can be hijacked but it leaves room for XSS since javascript can access to access token. Your approach has a bit of CSRF issue but XSS is minimized. Do I understand it right?

Last edited 11 months ago by Hao
8 months ago

Great article, thank you for sharing. What about the case of a public pc and the user does not check the “remember me” option and closes the browser without logging out. How should we handle such cases?

Refresh token should not be stored but what should we do?

8 months ago

In the log-in process we expect the client to send the id of the user, usually a client will just log-in with email/username and password, am I missing something?

8 months ago

I’ve read that unless your backend stores sessions, it is quite pointless to use cookies instead of localstorage. Is this true? To what extent?

6 months ago

how long does the refresh token last compared to access token?

3 months ago

This is a really cool approach. I believe that the cookie approach is often required with SSR frameworks such as NextJs as there is not the 2 step approach of React. Get the JS then process and make and follow up call to the server for things such as authorisations. Also in the micro-services world it is necessary to have a stateless approach.

2 months ago

When the access token is expired we can check refresh one and generate both new tokens and set them as cookies. Thus, the client won’t need to call /refresh endpoint.
But without cookies ( like auth in WebSocket transport in socket.io ), i like the /refresh endpoint.