DEV Community

Cover image for Refresh Token implementation in Reactjs
Chafroud Tarek
Chafroud Tarek

Posted on

Refresh Token implementation in Reactjs

In this tutorial, you will learn how to use refresh tokens to maintain access to a user's resources in your React application. Refresh tokens allow the application to obtain a new access token without requiring the user to re-authenticate, making it a useful tool for long-lived or background applications. Follow along as we walk through the process of implementing refresh token functionality in React.

You can check my previous post about Refresh Token implementation in nodejs

👋 You can also check my weekly posts in Linkedin

Requirements

  • Basic knowledge of reactjs.
  • Basic Knowledge of axios.

To get started, create a new file in your React app which you can name "axios.js" and place in a "helpers" folder, or anywhere else in your app as desired. This file will be used to set up and customize your application's HTTP requests with the axios library, and it will include all the logic for your refresh token implementation. It's a simple process, so let's get started!

start with :

const customFetch = axios.create({
  baseURL: "http://localhost:3000/api/",
  headers: {
    "Content-type": "application/json",
  },
  withCredentials: true,
});

Enter fullscreen mode Exit fullscreen mode

This code creates a custom instance of the axios library with a specific configuration.

  • The baseURL field sets the base URL for all API endpoints that this instance will be used to call.In this case, it is set to "http://localhost:3000/api/".
  • The headers object defines the default headers that will be sent with every request made using this instance.
  • the withCredentials a boolean value that indicates whether or not to send the "Cookie" header with requests. When set to true, the "Cookie" header will be sent with every request.

You can use this custom instance of axios to make HTTP requests in the same way as the default axios object. The main advantage of using this custom instance is that it includes the default configuration specified in this code, so you don't have to include these options in every request.

â–Ēī¸ To clarify, this interceptor can also be added for the purpose of retrieving the accessToken from local storage and including it in the Authorization header of the requests.

customFetch.interceptors.request.use(
  async (config) => {
    const token = getTokenFromLocalStorage();
    if (token) {
      config.headers["Authorization"] = ` bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);
Enter fullscreen mode Exit fullscreen mode

Now we will focus on the crucial part of this blog: the code below is responsible for refreshing the access token by sending a request to the server with the refresh token and receiving a new access token in the response.

const refreshToken = async () => {
  try {
    const resp = await customFetch.get("auth/refresh");
    console.log("refresh token", resp.data);
    return resp.data;
  } catch (e) {
    console.log("Error",e);   
  }
};

Enter fullscreen mode Exit fullscreen mode

This is a function that sends a GET request to the "auth/refresh" endpoint using the customFetch instance of axios. If the request is successful, the function logs the response data to the console and returns it. If the request fails, the function logs the error to the console

â–Ēī¸ The important interceptor for today is the code below:

customFetch.interceptors.response.use(
  (response) => {
    return response;
  },
  async function (error) {
    const originalRequest = error.config;
    if (error.response.status === 403 && !originalRequest._retry) {
      originalRequest._retry = true;

      const resp = await refreshToken();

      const access_token = resp.response.accessToken;

      addTokenToLocalStorage(access_token);
      customFetch.defaults.headers.common[
        "Authorization"
      ] = `Bearer ${access_token}`;
      return customFetch(originalRequest);
    }
    return Promise.reject(error);
  }
);
Enter fullscreen mode Exit fullscreen mode

The interceptor function takes two arguments: a response object containing the response data, and an error object in case of a rejected response. The function returns the response object or a rejected Promise with the error object.

The interceptor first checks if the response has a status of 403 (Forbidden) and if the _retry property of the original request is not set. If both conditions are met, it sets the _retry property to true and calls the refreshToken function to attempt to refresh the access token. If the refresh is successful, the new access token is added to local storage and the Authorization header of the customFetch instance is updated with the new token. The original request is then retried with the updated customFetch instance. If the refresh is not successful or if the original response did not have a status of 403, the interceptor returns a rejected Promise with the error object.

This interceptor will be called for every response received by the customFetch instance of axios, and will attempt to refresh the access token and retry the request if the access token has expired and the server returns a 403 status code.

In conclusion: In this tutorial, we learned how to use refresh tokens to maintain access to a user's resources in a React application. We set up a custom instance of the axios library with a specific configuration, including an interceptor to add an Authorization header with a bearer token to every request. We also implemented another interceptor to handle expired access tokens by attempting to refresh the token and retrying the original request. With these techniques, we can keep our application authenticated and maintain access to the user's resources without requiring the user to continually provide their login credentials.

Top comments (10)

Collapse
 
ikarov profile image
KarovInal • Edited

What would happen if an app made two requests with an expired access token?
Recently I found this issue:

  1. The first request gets a 403 response and immediately asks for the refresh-token endpoint.
  2. The second request also gets a 403 response and also makes a request to refresh-token endpoint
  3. The first refresh-token endpoint provides you new access and refresh tokens (the old refresh token isn't valid because this is how the refresh-token rotation works).
  4. The second refresh-token endpoint provides you an error, like "invalid refresh-token". Because you're trying to request a new access token using the old refresh token. (see the step 3).

I thought about this problem but didn't find a solution ☚ī¸

Collapse
 
chafroudtarek profile image
Chafroud Tarek

It rarely happens, but if it does, a possible solution is to limit it by the number of requests

Collapse
 
cederron profile image
cederron

Well, im here because its happening to me

Thread Thread
 
chafroudtarek profile image
Chafroud Tarek

i have a solution just follow these steps.

1/Create a variable, such as requestHolder or requestQueue, to hold the failed requests. This will be an array to store the requests that encountered a 403 error due to an expired access token.

2/ In the middleware, when a request receives a 403 response, add the failed request to the requestHolder array. This ensures that the failed request is queued for later retry.

3/Implement the logic to refresh the access token and retry the failed requests. The code snippet you provided suggests using a flag, isrefreshingAccessToken, to ensure that only one token refresh request is made at a time. Once the flag is set, the refreshAccessToken() function is called to obtain a new access token. After obtaining the new token, the retryFailedRequests(newAccessToken) function is invoked to iterate through the requestHolder array and modify the next request with the new access token.

4/The refreshAccessToken() function should handle the process of obtaining new tokens from the API. It could make a request to the refresh-token endpoint and return the new access token.

5/The retryFailedRequests(newAccessToken) function should iterate through the requestHolder array, shifting requests one by one, and update the Authorization header of each request with the new access token. Then, the modified request can be sent using customFetch to retry the request. This process continues until all failed requests in the requestHolder array have been retried.

By following these steps, you can implement a solution where failed requests with expired access tokens are queued and retried after obtaining a new access token. This approach helps minimize unnecessary token refresh requests and ensures that all requests are processed with a valid access token. If you have further questions or need more clarification, please let me know.

Thread Thread
 
gabrielsimas profile image
Gabriel Simas

An Good Idea would be store the datetime for token expiration, here in my endpoints I'm always return that information.

Collapse
 
dhruv21dk profile image
dhruv21dk
Collapse
 
omobayode6 profile image
Omobayode Festus • Edited

A link to the github code will help.
Is all these code in one directory that you named axios.js?
How do I use it in API call that require authorization?

Thanks

Collapse
 
chafroudtarek profile image
Chafroud Tarek

Just export the customFetch or the axiosInstance that you created, and use it whenever you need.

Collapse
 
hanzlaharoon profile image
Hanzla Haroon

How can we update the auth context with new authentication token?

Collapse
 
chafroudtarek profile image
Chafroud Tarek

There are different ways to update the context. One of them is by using an Axios interceptor just update the context when you get the new token . Alternatively, you can listen to changes in the local storage, and when a new token is available, simply store it in the local storage and it's done.