Axios interceptors and asynchronous login
Solution 1
Just use another promise :D
axios.interceptors.response.use(undefined, function (err) {
return new Promise(function (resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
serviceRefreshLogin(
getRefreshToken(),
success => {
setTokens(success.access_token, success.refresh_token)
err.config.__isRetryRequest = true
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
axios(err.config).then(resolve, reject);
},
error => {
console.log('Refresh login error: ', error);
reject(error);
}
);
}
throw err;
});
});
If your enviroment doesn't suport promises use polyfill, for example https://github.com/stefanpenner/es6-promise
But, it may be better to rewrite getRefreshToken to return promise and then make code simpler
axios.interceptors.response.use(undefined, function (err) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
return getRefreshToken()
.then(function (success) {
setTokens(success.access_token, success.refresh_token) ;
err.config.__isRetryRequest = true;
err.config.headers.Authorization = 'Bearer ' + getAccessToken();
return axios(err.config);
})
.catch(function (error) {
console.log('Refresh login error: ', error);
throw error;
});
}
throw err;
});
Demo https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview
Solution 2
Could do it in the request instead of the response, and it'd probably be cleaner since it'd avoid hitting the server when the access token's expired. Copying from this article:
function issueToken() {
return new Promise((resolve, reject) => {
return client({
...
}).then((response) => {
resolve(response);
}).catch((err) => {
reject(err);
});
});
}
client.interceptors.request.use((config) => {
let originalRequest = config;
if (tokenIsExpired && path_is_not_login) {
return issueToken().then((token) => {
originalRequest['Authorization'] = 'Bearer ' + token;
return Promise.resolve(originalRequest);
});
}
return config;
}, (err) => {
return Promise.reject(err);
});
Related videos on Youtube
Dmitry Shvedov
I've been working as a firmware developer for a few years, and now biting on the granite of web developement
Updated on April 12, 2020Comments
-
Dmitry Shvedov about 4 years
I'm implementing token authentication in my web app. My
access token
expires every N minutes and than arefresh token
is used to log in and get a newaccess token
.I use Axios for all my API calls. I have an interceptor set up to intercept
401
responses.axios.interceptors.response.use(undefined, function (err) { if (err.status === 401 && err.config && !err.config.__isRetryRequest) { serviceRefreshLogin( getRefreshToken(), success => { setTokens(success.access_token, success.refresh_token) }, error => { console.log('Refresh login error: ', error) } ) err.config.__isRetryRequest = true err.config.headers.Authorization = 'Bearer ' + getAccessToken() return axios(err.config); } throw err })
Basically, as I intercept a 401 response, I want to do a login and than retry the original rejected request with the new tokens. My
serviceRefreshLogin
function callssetAccessToken()
in itsthen
block. But the problem is that thethen
block happens later than thegetAccessToken()
in the interceptor, so the retry happens with the old expired credentials.getAccessToken()
andgetRefreshToken()
simply return the existing tokens stored in the browser (they manage localStorage, cookies, etc).How would I go about ensuring statements do not execute until a promise returns?
(Here's a corresponding issue on github: https://github.com/mzabriskie/axios/issues/266)
-
Dmitry Shvedov about 8 yearsUnfortunately that doesn't work as an interceptor. When I try this, the request gets retried only AFTER the original promise gets rejected.
-
Dmitry Shvedov about 8 yearsSorry, added a clarification - I don't see much sense in making getRefreshToken() into a promise. It's a function that manages stuff local to the browser - there is always something available there. Refresh token never expires BTW.
-
Rayjax about 7 yearsjust a quick note, for me err.status is undefined, used err.response.status instead