0

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;
  });
Green Train
  • 223
  • 3
  • 14

1 Answers1

1

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
})
User 28
  • 4,863
  • 1
  • 20
  • 35
  • I updated the code to use this approach, however, it's still sending more than 1 refresh token request, I have a Vue component that fires 3 requests in `mounted()` and all three are sending refresh token request to the API which causes an issue because the API removes the refresh token from the database after a refresh is done. I appreciate your help. – Green Train Oct 15 '20 at 16:51
  • @GreenTrain Ah, It's my bad. See [this](https://jsfiddle.net/kL8emrs0/15/). Open the DevTools and see the log and network request. – User 28 Oct 15 '20 at 18:56
  • What I changed is adding a new if condition at `await axios.refreshingToken`. At first I think it should be fine even if `refreshingToken` is undefined. But it makes code asynchronous so the second request cannot get value of `refreshingToken` which set from the first request. After adding if condition the first request will go through to set `refreshingToken` synchronously. – User 28 Oct 15 '20 at 19:03