Skip to content

Instantly share code, notes, and snippets.

@isumix
Created March 3, 2021 08:21
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isumix/28daba0671348c169b88d5176eab5e99 to your computer and use it in GitHub Desktop.
Save isumix/28daba0671348c169b88d5176eab5e99 to your computer and use it in GitHub Desktop.
auto-login/refresh token with axios interceptors and queue pending requests
import axios, { AxiosError } from 'axios';
import { getLoginData, LoginFormInputs, openLoadingScreen } from 'my-gui-lib';
const STORAGE_NAME = 'auth';
const HEADER_NAME = 'Authorization';
let _refreshToken = '';
let _authorizing: Promise<void> | null = null;
{ // init
const str = localStorage.getItem(STORAGE_NAME);
if(str) {
const { accessToken, refreshToken } = JSON.parse(str);
axios.defaults.headers.common[HEADER_NAME] = accessToken;
_refreshToken = refreshToken;
}
}
const login = (accessToken: string, refreshToken: string) => {
axios.defaults.headers.common[HEADER_NAME] = accessToken;
_refreshToken = refreshToken;
localStorage.setItem(STORAGE_NAME, JSON.stringify({ accessToken, refreshToken }));
};
const logout = () => {
delete axios.defaults.headers.common[HEADER_NAME];
_refreshToken = '';
localStorage.removeItem(STORAGE_NAME);
};
const authorize = async () => {
let loginData: LoginFormInputs | undefined, errorMessage;
for (;;) {
loginData = await getLoginData({ ...loginData, errorMessage });
const closeLoadingScreen = openLoadingScreen();
try {
const response = await axios.post('/auth/signin', loginData);
const { accessToken, refreshToken } = response.data;
login(accessToken, refreshToken);
break;
}
catch (e) {
errorMessage = 'Invalid username or password! Please try again.';
}
finally {
closeLoadingScreen();
}
}
};
const refresh = async () => {
const closeLoadingScreen = openLoadingScreen();
try {
const response = await axios.post('/auth/refresh', { refreshToke: _refreshToken });
const { accessToken, refreshToken } = response.data;
login(accessToken, refreshToken);
}
catch (e) {
logout();
}
closeLoadingScreen();
};
axios.interceptors.response.use(undefined, async (error: AxiosError) => {
if(error.response?.status !== 401) {
return Promise.reject(error);
}
// create pending authorization
_authorizing ??= (_refreshToken ? refresh : authorize)()
.finally(() => _authorizing = null)
.catch(error => Promise.reject(error));
const originalRequestConfig = error.config;
delete originalRequestConfig.headers[HEADER_NAME]; // use from defaults
// delay original requests until authorization has been completed
return _authorizing.then(() => axios.request(originalRequestConfig));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment