Axios response interceptor for refreshing token keeps firing in Vue 3

4.2k Views Asked by At

I'm trying to implement a refresh token with Vue 3 and Java for backend. It is working but interceptor keeps firing.

The logic: On every request there's a JWT Authorization header that authenticates the user. If that expires, there's a cookie endpoint in place ready to refresh the JWT.

I am using axios and interceptor response to check if the client gets a 401 to try and refresh the JWT. The cookie may be valid or not.

The problem is that the interceptor to refresh the JWT never stops firing, and I think I have something wrong with the synchronization of the requests. Below is my code:

Api.js:

import axios from "axios";


 const instance = axios.create({
  baseURL: "MY_URL",
});


export default instance;

token.service.js:

class TokenService {
  getLocalRefreshToken() {
    const user = JSON.parse(localStorage.getItem("user"));
    return user?.refreshToken;
  }
  getLocalAccessToken() {
    const user = JSON.parse(localStorage.getItem("user"));
    return user?.accessToken;
  }
  updateLocalAccessToken(token) {
    let user = JSON.parse(localStorage.getItem("user"));
    user.accessToken = token;
    localStorage.setItem("user", JSON.stringify(user));
  }
  getUser() {
    return JSON.parse(localStorage.getItem("user"));
  }
  setUser(user) {
    // eslint-disable-next-line no-console
    console.log(JSON.stringify(user));
    localStorage.setItem("user", JSON.stringify(user));
  }
  removeUser() {
    localStorage.removeItem("user");
  }
}
export default new TokenService();

setupInterceptors.js:

import axiosInstance from "./api";
import TokenService from "./token.service";
const setup = (store) => {
  axiosInstance.interceptors.request.use(
    (config) => {
      const token = TokenService.getLocalAccessToken();
      if (token) {
        config.headers["Authorization"] = 'Bearer ' + token;
      }
      return config;
    },
    (error) => {
      return Promise.reject(error);
    }
  );

  axiosInstance.interceptors.response.eject()

  axiosInstance.interceptors.response.use(
    (res) => {
      return res;
    },
    async (err) => {
      const originalConfig = err.config;
      if (originalConfig.url !== "/auth/login" && err.response) {
        // Access Token was expired
        if (err.response.status === 401 && !originalConfig._retry) {
          originalConfig._retry = true;
          try {
            const rs = await axiosInstance.post("/auth/refreshtoken", {
              refreshToken: TokenService.getLocalRefreshToken(),
            });
            const { accessToken } = rs.data;
            store.dispatch("auth/refreshToken", accessToken);
            TokenService.updateLocalAccessToken(accessToken);
            return axiosInstance(originalConfig);
          } catch (_error) {
            return Promise.reject(_error);
          }
        }
      }
      return Promise.reject(err);
    }
  );
};
export default setup;
3

There are 3 best solutions below

0
On

try clean el token authorization before send request refresh, by example in mutations(vuex)

clearAccessToken(state) {
   state.access_token = ''
   TokenService.removeAccessTokenApi();
},
0
On

For me it was fixed by not using the same axios instance for the refresh token request.

0
On

try this out and make sure you use another instance of Axios for the refresh token request

// to be used by the interceprot
firstAxiosInstance = axios.create({ baseURL: MY_URL });
//to be used by the refresh token API call
const secondAxiosInstance = axios.create({ baseURL: MY_URL});
firstAxiosInstance.interceptors.response.use(
  (res) => {
    return res;
  },
  async (err) => {
    // this is the original request that failed
    const originalConfig = err.config;

    // decoding the refresh token at this point to get its expiry time
    const decoded = jwt.decode(localStorage.getItem('refreshToken'));

    // check if the refresh token has expired upon which logout user
    if (decoded.exp < Date.now() / 1000) {
      store.commit('logout');
      router.push('/');
    }

    // get new access token and resend request if refresh token is valid
    if (decoded.exp > Date.now() / 1000) {
      if (err.response.status === 401) {
        originalConfig._retry = true;
        try {
          const rs = await requestService.post('/api-v1/token/refresh/', {
            refresh: localStorage.getItem('refreshToken'),
          });
          store.commit('update_aceess_token', rs.data);
          err.config.headers.Authorization = `Bearer ${rs.data.access}`;
          return new Promise((resolve, reject) => {
            requestService
              .request(originalConfig)
              .then((response) => {
                resolve(response);
              })
              .catch((e) => {
                reject(e);
              });
          });
        } catch (_error) {
          return Promise.reject(_error);
        }
      }
    }
    return Promise.reject(err);
  },
);