Search code examples
javascriptvue.jsaxiosvuex

Refresh JWT Token using Axios request interceptor (interceptors.request) - Multiple requests at once problem


I'm using this code to intercept every HTTP call in Axios, in order to keep the user logged in by refreshing the JWT token when it expires:

const { token } = window.localStorage;

const axiosInstance = axios.create({
    baseURL: process.env.VUE_APP_API_ENDPOINT,
    withCredentials: false,
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: token && token.length > 0 ? `Bearer ${token}` : ""
    }
  });

axiosInstance.interceptors.request.use(request => {
    if (request.url.endsWith("refresh")) {  // prevent infinite loop
      return request;
    }

const { expiryDate } = window.localStorage;
const currentTimestamp = Math.round(new Date().getTime() / 1000);

if (expiryDate && currentTimestamp >= expiryDate) {
  console.log("token expired.");

  return store
    .dispatch("auth/refreshToken")  // refreshToken action will place the token in localStorage.token
    .then(() => {
      const newToken = window.localStorage.token; 

      request.headers.Authorization = `Bearer ${newToken}`;

      return Promise.resolve(request);
    })
    .catch(() => Promise.resolve(request));
}
console.log("token not expired.");

return request;

});

This used to work fine until I added more requests at page load, each request is trying to refresh the token which causes an error in the back-end. How do I solve this problem? I have searched a lot but all the solutions I found were for interceptors.response like this one, I'd like to use interceptors.request because logging in is optional and there will be no 401 response if the user is not logged in.

EDIT: Based on the answer from User 28 below the code has changed to:

axiosInstance.interceptors.request.use(async config => {
    if (config.url.endsWith("refresh")) {
      // if we are making a refresh token call, return it to prevent infinite loop
      return config;
    }

    await axiosInstance.refreshingToken;

    const { expiryDate } = window.localStorage; // token expiry date

    const currentTimestamp = Math.round(new Date().getTime() / 1000);

    if (expiryDate && currentTimestamp >= expiryDate) {
      axiosInstance.refreshingToken = store.dispatch("auth/refreshToken"); // this will update the token in localstorage
      await axiosInstance.refreshingToken;
    }

    const { token } = window.localStorage; // grab the token from localstorage

    config.headers.Authorization = `Bearer ${token}`;

    return config;
  });

Solution

  • You need a global variable to determine you have to wait or not. The easiest way is to assign it to the axios instance.

    Example Code:

    axios.interceptors.request.use(async config => {
      if (isRefreshTokenRequest) {
        return config
      }
    
      if (axios.refreshingToken) {
        await axios.refreshingToken
      }
    
      if (isTokenExpired) {
        axios.refreshingToken = store.dispatch('auth/refreshToken')
        await axios.refreshingToken
      }
    
      // set authorization header
    
      return config
    })