-
-
Save Godofbrowser/bf118322301af3fc334437c683887c5f to your computer and use it in GitHub Desktop.
// 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); | |
}; |
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.
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
@Godofbrowser and how is it used, is there an example?
Thank you !
Thanks!
It work very well, I just change the forEach
for a regular for of
Thank you. Cool solution!
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;
// ...
})
`
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!
This works perfectly. Thank you so much. This is a life saver.
thanks, @Godofbrowser , work as expected
Try this solution maybe it will help you to solve your problem=)
https://stackoverflow.com/a/66288792/12174949
Thanks so much for this! Just what I needed to help properly implement refresh token handling!
Thank you very much. I spent all day for this case. You saved my day <3
Thank you very much ! awesome ;)
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
This is perfect. Thanks a lot.
It helped me a lot. Thank you so much!
고맙습니다😄👍🙏
thank u very much, design is very human :v
Thx man, u save me.
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?
Hi @laurelmishra Is this error on your custom project or on the demo?
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.
Thank you it help me a lot !
In the following file [axios.refresh_token.1.js] what to do if the 401 error comes once again in the refresh token request?
@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();
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.
thank you, it worked for me.
@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!