reactjscookiesaxiosqwikjs

Axios interceptor for token refresh


I am using Axios interceptor to get new Access token with refresh token. The following code creates new access token from server, but I cannot find the cookie in the browser.

Works fine, but the browser struck on loading.

It could be much helpful, if someone can find where the issue is.

Advance thanks.


interface RetryQueueItem {
  resolve: (value?: any) => void;
  reject: (error?: any) => void;
  config: AxiosRequestConfig;
}

const fetchAxiosClient: AxiosInstance = axios.create({
  baseURL: 'http://localhost:8080/api/v1',
  timeout: 60000
});
fetchAxiosClient.defaults.headers.common = {
  'Content-Type': 'application/json',
  'Accept': 'application/json'
} as headers;

export async function createAxiosInstance(requestEvent) {

  // Auth token to request header
  fetchAxiosClient.interceptors.request.use(
    (config: InternalAxiosRequestConfig) => {
      const token = getAccessToken(requestEvent.cookieObj).access_token;
      if (token) {
        config.headers['Authorization'] = `Bearer ${token}`;
      }
      config.headers['Content-Type'] = 'application/json';
      return config;
    },
    error => Promise.reject(error)
  );

  const refreshAndRetryQueue: RetryQueueItem[] = [];
  let isRefreshing = false;

  fetchAxiosClient.interceptors.response.use(
    function (response: AxiosResponse) {
      return response;
    },
    async function (error: any) {
      const originalRequest: AxiosRequestConfig = error.config;
      if (error.response && error.response.status === 401) {
        if (!isRefreshing) {
          isRefreshing = true;
          try {
            const refreshToken = getRefreshToken(requestEvent.cookieObj);
            if (refreshToken) {
              await axios.post(
                `${fetchAxiosClient?.defaults.baseURL}${REFRESH_TOKEN}`,
                {
                  refresh_token: refreshToken,
                }
              ).then(async (response: any) => {
                const cookies = response.headers['set-cookie'] as string[];
                const accessToken = getToken(cookies[0], APP_CONSTANTS.ACCESS_TOKEN);
                setToken(requestEvent.cookieObj, accessToken!);
                return fetchAxiosClient!(originalRequest);
              }).catch((error) => {
                requestEvent.cookieObj.delete(APP_CONSTANTS.ACCESS_TOKEN, { path: '/' });
                throw requestEvent.redirect(302, '/login');
                //return Promise.reject(error);
              });
              refreshAndRetryQueue.forEach(({ config, resolve, reject }) => {
                fetchAxiosClient!(config)
                  .then((response) => resolve(response))
                  .catch((err) => reject(err));
              });
              refreshAndRetryQueue.length = 0;
            } else {
                requestEvent.cookieObj.delete(APP_CONSTANTS.ACCESS_TOKEN, { path: '/' });
              window.location.href = "/";
              return Promise.reject(error);
            }
          } catch (refreshError) {
            refreshAndRetryQueue.length = 0;
            requestEvent.cookieObj.delete(APP_CONSTANTS.ACCESS_TOKEN, { path: '/' });
          } finally {
            isRefreshing = false;
          }
        }
        return new Promise<void>((resolve, reject) => {
          refreshAndRetryQueue.push({ config: originalRequest, resolve, reject });
        });
      }
      return Promise.reject(error);
    }
  );
}

The new generated access token which will expire in 15 mins should set as cookie. The moment access token expired, new access token should be generated with refresh token.

But, after expiry of access token, while generating new access token the browser shows loading indicator and continuously busy.


Solution

  • I referred this

    https://blog.stackademic.com/refresh-access-token-with-axios-interceptors-in-react-js-with-typescript-bd7a2d035562

    working fine.

    export async function createAxiosInstance(cookieObj: Cookie) {
        interface FailedRequests {
          resolve: (value: AxiosResponse) => void;
          reject: (value: AxiosError) => void;
          config: AxiosRequestConfig;
          error: AxiosError;
        }
        // Add the auth token to every request
        fetchAxiosClient.interceptors.request.use(
          (config: InternalAxiosRequestConfig) => {
            const token = getAccessToken(cookieObj).access_token;
            if (token) {
              config.headers['Authorization'] = `Bearer ${token}`;
            }
            config.headers['Content-Type'] = 'application/json';
            return config;
          },
          error => Promise.reject(error)
        );
      
        let failedRequests: FailedRequests[] = [];
        let isTokenRefreshing = false;
      
        fetchAxiosClient.interceptors.response.use(
          function (response: AxiosResponse) {
            return response;
          },
          async (error: AxiosError) => {
            const status = error.response?.status;
            const originalRequestConfig = error.config!;
      
            if (status !== 401) {
              return Promise.reject(error);
            }
      
            if (isTokenRefreshing) {
              return new Promise((resolve, reject) => {
                failedRequests.push({
                  resolve,
                  reject,
                  config: originalRequestConfig,
                  error: error,
                });
              });
            }
      
            isTokenRefreshing = true;
            const refreshToken = getRefreshToken(cookieObj);
            try {
              const response = await fetchAxiosClient.post(
                `${fetchAxiosClient?.defaults.baseURL}${REFRESH_TOKEN}`,
                      {
                        refresh_token: refreshToken,
                      }
              );
              const cookies = response.headers['set-cookie'] as string[];
              const accessToken = getToken(cookies[0], APP_CONSTANTS.ACCESS_TOKEN);
              setToken(cookieObj, accessToken!);
              failedRequests.forEach(({ resolve, reject, config }) => {
                fetchAxiosClient(config)
                  .then((response) => resolve(response))
                  .catch((error) => reject(error));
              });
            } catch (_error: unknown) {
              console.error(_error);
              failedRequests.forEach(({ reject, error }) => reject(error));
              cookieObj.delete(APP_CONSTANTS.ACCESS_TOKEN, { path: '/' });
              return Promise.reject(error);
            } finally {
              failedRequests = [];
              isTokenRefreshing = false;
            }
            return fetchAxiosClient(originalRequestConfig);
          }
        );
      }