Skip to content

Instantly share code, notes, and snippets.

@mkjiau
Last active March 13, 2024 10:59
Show Gist options
  • Save mkjiau/650013a99c341c9f23ca00ccb213db1c to your computer and use it in GitHub Desktop.
Save mkjiau/650013a99c341c9f23ca00ccb213db1c to your computer and use it in GitHub Desktop.
Axios interceptors for token refreshing and more than 2 async requests available
let isRefreshing = false;
let refreshSubscribers = [];
const instance = axios.create({
baseURL: Config.API_URL,
});
instance.interceptors.response.use(response => {
return response;
}, error => {
const { config, response: { status } } = error;
const originalRequest = config;
if (status === 498) {
if (!isRefreshing) {
isRefreshing = true;
refreshAccessToken()
.then(newToken => {
isRefreshing = false;
onRrefreshed(newToken);
});
}
const retryOrigReq = new Promise((resolve, reject) => {
subscribeTokenRefresh(token => {
// replace the expired token and retry
originalRequest.headers['Authorization'] = 'Bearer ' + token;
resolve(axios(originalRequest));
});
});
return retryOrigReq;
} else {
return Promise.reject(error);
}
});
subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token));
}
@stanislav-sidorov-empeek
Copy link

stanislav-sidorov-empeek commented Feb 15, 2023

here is my axios instance file, all requests perform sequentially and if i get one 401 response, all other go to queue and relieve only after token refresh
so i don't get more then one 401

import axios, { AxiosError } from 'axios';
import { Store } from 'redux';
import axiosMiddlewareFactory from 'redux-axios-middleware';
import AppConfig from '~/config/appConfig';
import needError from '~/helpers/needError';
import { networkOffline, showError } from '~/modules/app/actions/AppActions';
import {
  IRefreshTokenData,
  logout,
  refreshToken,
  refreshTokenSuccess,
} from '~/modules/auth/actions/AuthActions';
import {
  getIsAuthenticated,
  getRefreshToken,
  getToken,
} from '~/modules/auth/AuthSelectors';
import { ErrorCodes } from '~/modules/auth/models';
import { getProfile } from '~/modules/settings/SettingsSelector';

type IRequestCb = (token: string) => void;

export const axiosClient = axios.create({
  baseURL: AppConfig.apiUrl,
  responseType: 'json',
});

let isRefreshing = false;
let refreshSubscribers: IRequestCb[] = [];
let refreshRetry = true;

const subscribeTokenRefresh = (cb: IRequestCb) => {
  refreshSubscribers.push(cb);
};

const onRefreshed = (token: string) => {
  refreshSubscribers.map(cb => cb(token));
};

const axiosMiddlewareOptions = {
  interceptors: {
    request: [
      ({ getState }: Store, request: any) => {
        const state = getState();
        const token = getToken(state);

        if (token) {
          request.headers.authorization = `Bearer ${token}`;
        }

        return request;
      },
    ],
    response: [
      {
        error: function ({ getState, dispatch }: Store, error: AxiosError) {
          const state = getState();
          const accessToken = getToken(state);
          const profile = getProfile(state);

          const { teamId, roleId } = profile || {};
          const isAuthenticated = getIsAuthenticated(state);

          if (error?.response?.status === 401) {
            const refresh = getRefreshToken(state);
            const originalRequest = error.config;

            const retryOrigReq = new Promise(resolve => {
              subscribeTokenRefresh((newToken: string) => {
                // replace the expired token and retry
                if (originalRequest.headers) {
                  originalRequest.headers.authorization = `Bearer ${newToken}`;
                }
                resolve(axios(originalRequest));
              });
            });

            if (!isRefreshing && accessToken && refresh && roleId && teamId) {
              isRefreshing = true;

              refreshToken({
                accessToken,
                refreshToken: refresh,
                roleId,
                teamId,
              })
                .then(
                  ({
                    accessToken: newAccessToken,
                    refreshToken: newRefreshToken,
                  }: IRefreshTokenData) => {
                    if (originalRequest.headers) {
                      originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
                      dispatch(
                        refreshTokenSuccess({
                          accessToken: newAccessToken,
                          refreshToken: newRefreshToken,
                          roleId,
                          teamId,
                        }),
                      );
                      refreshRetry = true;

                      onRefreshed(newAccessToken);
                    }
                  },
                )
                .catch(e => {
                  Bugsnag.notify(e);
                  if (
                    e.response?.data?.error &&
                    needError(e.response?.config)
                  ) {
                    dispatch(showError(e.response?.data.error));
                  } else if (refreshRetry) {
                    refreshRetry = false;
                  } else {
                    dispatch(
                      showError(
                        'Unable to restore session. Please login again',
                      ),
                    );
                    dispatch(logout());
                  }
                  return Promise.reject(error);
                })
                .finally(() => {
                  isRefreshing = false;
                });
            }

            return retryOrigReq;
          } else if (error?.response?.status === 403) {
            // user deactivated
            dispatch(
              showError(
                'Your account has been locked. Contact your support person to unlock it, then try again.',
              ),
            );
            dispatch(logout());
          } else if (
            [
              ErrorCodes.passwordExpired,
              ErrorCodes.accountDeleted,
              ErrorCodes.accountLocked,
            ].includes(error?.code as ErrorCodes)
          ) {
            if (isAuthenticated) {
              // password expired, account deleted, locked
              dispatch(showError(error.message));
              dispatch(logout());
            }
          } else {
            if (error.code === ErrorCodes.network) {
              dispatch(networkOffline());
            } else if (
              ((error.response?.data as { error: string })?.error ||
                error.message) &&
              needError(error.response?.config)
            ) {
              dispatch(
                showError(
                  (error.response?.data as { error: string })?.error ||
                    error.message,
                ),
              );
            }

            return Promise.reject(error);
          }
        },
      },
    ],
  },
};

const axiosMiddleware = axiosMiddlewareFactory(
  axiosClient,
  axiosMiddlewareOptions,
);

export default axiosMiddleware;

FishManHell

@RezaBakhshiNia
Copy link

here is my axios instance file, all requests perform sequentially and if i get one 401 response, all other go to queue and relieve only after token refresh so i don't get more then one 401

import axios, { AxiosError } from 'axios';
import { Store } from 'redux';
import axiosMiddlewareFactory from 'redux-axios-middleware';
import AppConfig from '~/config/appConfig';
import needError from '~/helpers/needError';
import { networkOffline, showError } from '~/modules/app/actions/AppActions';
import {
  IRefreshTokenData,
  logout,
  refreshToken,
  refreshTokenSuccess,
} from '~/modules/auth/actions/AuthActions';
import {
  getIsAuthenticated,
  getRefreshToken,
  getToken,
} from '~/modules/auth/AuthSelectors';
import { ErrorCodes } from '~/modules/auth/models';
import { getProfile } from '~/modules/settings/SettingsSelector';

type IRequestCb = (token: string) => void;

export const axiosClient = axios.create({
  baseURL: AppConfig.apiUrl,
  responseType: 'json',
});

let isRefreshing = false;
let refreshSubscribers: IRequestCb[] = [];
let refreshRetry = true;

const subscribeTokenRefresh = (cb: IRequestCb) => {
  refreshSubscribers.push(cb);
};

const onRefreshed = (token: string) => {
  refreshSubscribers.map(cb => cb(token));
};

const axiosMiddlewareOptions = {
  interceptors: {
    request: [
      ({ getState }: Store, request: any) => {
        const state = getState();
        const token = getToken(state);

        if (token) {
          request.headers.authorization = `Bearer ${token}`;
        }

        return request;
      },
    ],
    response: [
      {
        error: function ({ getState, dispatch }: Store, error: AxiosError) {
          const state = getState();
          const accessToken = getToken(state);
          const profile = getProfile(state);

          const { teamId, roleId } = profile || {};
          const isAuthenticated = getIsAuthenticated(state);

          if (error?.response?.status === 401) {
            const refresh = getRefreshToken(state);
            const originalRequest = error.config;

            const retryOrigReq = new Promise(resolve => {
              subscribeTokenRefresh((newToken: string) => {
                // replace the expired token and retry
                if (originalRequest.headers) {
                  originalRequest.headers.authorization = `Bearer ${newToken}`;
                }
                resolve(axios(originalRequest));
              });
            });

            if (!isRefreshing && accessToken && refresh && roleId && teamId) {
              isRefreshing = true;

              refreshToken({
                accessToken,
                refreshToken: refresh,
                roleId,
                teamId,
              })
                .then(
                  ({
                    accessToken: newAccessToken,
                    refreshToken: newRefreshToken,
                  }: IRefreshTokenData) => {
                    if (originalRequest.headers) {
                      originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
                      dispatch(
                        refreshTokenSuccess({
                          accessToken: newAccessToken,
                          refreshToken: newRefreshToken,
                          roleId,
                          teamId,
                        }),
                      );
                      refreshRetry = true;

                      onRefreshed(newAccessToken);
                    }
                  },
                )
                .catch(e => {
                  Bugsnag.notify(e);
                  if (
                    e.response?.data?.error &&
                    needError(e.response?.config)
                  ) {
                    dispatch(showError(e.response?.data.error));
                  } else if (refreshRetry) {
                    refreshRetry = false;
                  } else {
                    dispatch(
                      showError(
                        'Unable to restore session. Please login again',
                      ),
                    );
                    dispatch(logout());
                  }
                  return Promise.reject(error);
                })
                .finally(() => {
                  isRefreshing = false;
                });
            }

            return retryOrigReq;
          } else if (error?.response?.status === 403) {
            // user deactivated
            dispatch(
              showError(
                'Your account has been locked. Contact your support person to unlock it, then try again.',
              ),
            );
            dispatch(logout());
          } else if (
            [
              ErrorCodes.passwordExpired,
              ErrorCodes.accountDeleted,
              ErrorCodes.accountLocked,
            ].includes(error?.code as ErrorCodes)
          ) {
            if (isAuthenticated) {
              // password expired, account deleted, locked
              dispatch(showError(error.message));
              dispatch(logout());
            }
          } else {
            if (error.code === ErrorCodes.network) {
              dispatch(networkOffline());
            } else if (
              ((error.response?.data as { error: string })?.error ||
                error.message) &&
              needError(error.response?.config)
            ) {
              dispatch(
                showError(
                  (error.response?.data as { error: string })?.error ||
                    error.message,
                ),
              );
            }

            return Promise.reject(error);
          }
        },
      },
    ],
  },
};

const axiosMiddleware = axiosMiddlewareFactory(
  axiosClient,
  axiosMiddlewareOptions,
);

export default axiosMiddleware;

FishManHell

Hi, I hope you're doing well.
can you please show an example that how to make a request?
could I just use axios client?

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment