Skip to content

Instantly share code, notes, and snippets.

@Godofbrowser
Forked from culttm/axios.refresh_token.js
Last active April 26, 2024 12:57
Show Gist options
  • Save Godofbrowser/bf118322301af3fc334437c683887c5f to your computer and use it in GitHub Desktop.
Save Godofbrowser/bf118322301af3fc334437c683887c5f to your computer and use it in GitHub Desktop.
Axios interceptor for refresh token when you have multiple parallel requests. Demo implementation: https://github.com/Godofbrowser/axios-refresh-multiple-request
// for multiple requests
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
})
failedQueue = [];
}
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function(resolve, reject) {
failedQueue.push({resolve, reject})
}).then(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return axios(originalRequest);
}).catch(err => {
return Promise.reject(err);
})
}
originalRequest._retry = true;
isRefreshing = true;
const refreshToken = window.localStorage.getItem('refreshToken');
return new Promise(function (resolve, reject) {
axios.post('http://localhost:8000/auth/refresh', { refreshToken })
.then(({data}) => {
window.localStorage.setItem('token', data.token);
window.localStorage.setItem('refreshToken', data.refreshToken);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.token;
originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
processQueue(null, data.token);
resolve(axios(originalRequest));
})
.catch((err) => {
processQueue(err, null);
reject(err);
})
.finally(() => { isRefreshing = false })
})
}
return Promise.reject(error);
});
// Intercept and refresh expired tokens for multiple requests (same implementation but with some abstractions)
//
// HOW TO USE:
// import applyAppTokenRefreshInterceptor from 'axios.refresh_token.2.js';
// import axios from 'axios';
// ...
// applyAppTokenRefreshInterceptor(axios); // register the interceptor with all axios instance
// ...
// - Alternatively:
// const apiClient = axios.create({baseUrl: 'example.com/api'});
// applyAppTokenRefreshInterceptor(apiClient); // register the interceptor with one specific axios instance
// ...
// - With custom options:
// applyAppTokenRefreshInterceptor(apiClient, {
// shouldIntercept: (error) => {
// return error.response.data.errorCode === 'EXPIRED_ACCESS_TOKEN';
// }
// ); // register the interceptor with one specific axios instance
//
// PS: You may need to figure out some minor things yourself as this is just a proof of concept and not a tutorial.
// Forgive me in advance
const shouldIntercept = (error) => {
try {
return error.response.status === 401
} catch (e) {
return false;
}
};
const setTokenData = (tokenData = {}, axiosClient) => {
// If necessary: save to storage
// tokenData's content includes data from handleTokenRefresh(): {
// idToken: data.auth_token,
// refreshToken: data.refresh_token,
// expiresAt: data.expires_in,
// };
};
const handleTokenRefresh = () => {
const refreshToken = window.localStorage.getItem('refreshToken');
return new Promise((resolve, reject) => {
axios.post('http://localhost:8000/auth/refresh', { refreshToken })
.then(({data}) => {
const tokenData = {
idToken: data.auth_token,
refreshToken: data.refresh_token,
expiresAt: data.expires_at,
};
resolve(tokenData);
})
.catch((err) => {
reject(err);
})
});
};
const attachTokenToRequest = (request, token) => {
request.headers['Authorization'] = 'Bearer ' + token;
// If there is an edge case where access token is also set in request query,
// this is also a nice place to add it
// Example: /orders?token=xyz-old-token
if (/\/orders/.test(request.url)) {
request.params.token = token;
}
};
export default (axiosClient, customOptions = {}) => {
let isRefreshing = false;
let failedQueue = [];
const options = {
attachTokenToRequest,
handleTokenRefresh,
setTokenData,
shouldIntercept,
...customOptions,
};
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
const interceptor = (error) => {
if (!options.shouldIntercept(error)) {
return Promise.reject(error);
}
if (error.config._retry || error.config._queued) {
return Promise.reject(error);
}
const originalRequest = error.config;
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({resolve, reject})
}).then(token => {
originalRequest._queued = true;
options.attachTokenToRequest(originalRequest, token);
return axiosClient.request(originalRequest);
}).catch(err => {
return Promise.reject(error); // Ignore refresh token request's "err" and return actual "error" for the original request
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise((resolve, reject) => {
options.handleTokenRefresh.call(options.handleTokenRefresh)
.then((tokenData) => {
options.setTokenData(tokenData, axiosClient);
options.attachTokenToRequest(originalRequest, tokenData.idToken);
processQueue(null, tokenData.idToken);
resolve(axiosClient.request(originalRequest));
})
.catch((err) => {
processQueue(err, null);
reject(err);
})
.finally(() => {
isRefreshing = false;
})
});
};
axiosClient.interceptors.response.use(undefined, interceptor);
};
@ElpixZero
Copy link

ElpixZero commented Jul 4, 2020

@Godofbrowser, Hello, Mister!

I think, you forgot to return a response data in 54 line of your code. Without it, the component, that called this axios-request didn't get the data of his request. Instead of it, it will get 'undefined', because you return nothing.

If i am wrong, it'll be good if you feedback me.

Thanks!

@Godofbrowser
Copy link
Author

Hello @ElpixZero , that's valid. In my most recent implementation i used finally instead so that might be the reason i didn't notice that earlier...

I see that @andynoir pinted that out too and gave the solution. Thanks @andynoir and sorry about the late reply.

@Godofbrowser
Copy link
Author

Godofbrowser commented Jul 7, 2020

I'm updated the snippet to something close to what i currently use in production without issues so far.
Please see second file axios.refresh_token.2.js

@ZoHayk
Copy link

ZoHayk commented Jul 23, 2020

@Godofbrowser and how is it used, is there an example?

@esidate
Copy link

esidate commented Aug 17, 2020

Thank you !

@psmever
Copy link

psmever commented Sep 2, 2020

Thanks!

@josera21
Copy link

It work very well, I just change the forEach for a regular for of

@mkkravchenkoo
Copy link

Thank you. Cool solution!

@adntin
Copy link

adntin commented Oct 20, 2020

If more than one request occurs at the same time when the application is started, and the refresh token returns faster, it will cause other requests to replace the token again.
Solutions:
`
let isRefreshed = false;
let newData = {};

if (isRefreshed) {
originalRequest.headers['Authorization'] = 'Bearer ' + newData.token;
return axios(config);
}
if (isRefreshing) {
// ...
}

axios.post('http://localhost:8000/auth/refresh', { refreshToken })
.then(({data}) => {
isRefreshed = true;
newData = data;
// ...
})

`

@mortezashojaei
Copy link

I had to add on line 35:

if(originalRequest.headers.Authorization !== getAuth()) { originalRequest.headers['Authorization'] = getAuth(); return Promise.resolve(client(originalRequest)); }

This because in concurrent requests some causing 401 response can be sent BEFORE new token is issued and return AFTER a new token is just issued. So I check if the current token (output of getAuth() is still the same of the original request in my queue.

you saved me!

@PoojaSpiking
Copy link

This works perfectly. Thank you so much. This is a life saver.

@yjose
Copy link

yjose commented Jan 30, 2021

thanks, @Godofbrowser , work as expected

@u1810291
Copy link

Try this solution maybe it will help you to solve your problem=)
https://stackoverflow.com/a/66288792/12174949

@nicholaszuccarelli
Copy link

Thanks so much for this! Just what I needed to help properly implement refresh token handling!

@peterhoang1401
Copy link

peterhoang1401 commented Jul 23, 2021

Thank you very much. I spent all day for this case. You saved my day <3

@SaharAsadii
Copy link

Thank you very much ! awesome ;)

@doanthanh2310
Copy link

Hi @Godofbrowser,
I have an issue when implement axios.refresh_token.2.js to my code. I call multiple request when app start, and the first request always return problem:UNKNOWN_ERROR. Do you have any solution to fix it. Thank you

@sollych
Copy link

sollych commented Nov 15, 2021

This is perfect. Thanks a lot.

@yoochangoh
Copy link

It helped me a lot. Thank you so much!

@jjjlyn
Copy link

jjjlyn commented Dec 9, 2021

고맙습니다😄👍🙏

@khanglox868686
Copy link

thank u very much, design is very human :v

@RpThiagoluiz
Copy link

Thx man, u save me.

@laurelmishra
Copy link

I got an error Build Error when running script yarn dev https://github.com/Godofbrowser/axios-refresh-multiple-request
The error message is:

Duplicate plugin/preset detected.
If you'd like to use two separate instances of a plugin,
they need separate names, e.g.

  plugins: [
    ['some-plugin', {}],
    ['some-plugin', {}, 'some unique name'],
  ]

How can I fix this?

@Godofbrowser
Copy link
Author

Hi @laurelmishra Is this error on your custom project or on the demo?

@Pipoteex
Copy link

Hello friends, can you help me with the same example only with React? I'm trying to do the same thing, but the refresh token is executed multiple times anyway.

@daniel-agbato
Copy link

Thank you it help me a lot !

@harshitsingla13
Copy link

In the following file [axios.refresh_token.1.js] what to do if the 401 error comes once again in the refresh token request?

@Godofbrowser
Copy link
Author

@harshitsingla13 you can create a separate axios instance that will be used for every other request

const axiosWithRefresh = axios.create();

So that when you try to refresh you can use the global axios that doesn't include the refresh interceptor

axios.post();

@harshitsingla13
Copy link

What to do if I have encrypted request and I always decrypt at frontend. I encrypt every request before sending, so where do I have to decrypt as to refresh the token.

@tnson1307
Copy link

thank you, it worked for me.

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