J'implémente l'authentification par jeton dans mon application Web. Mon access token
Expire toutes les N minutes et un refresh token
Est utilisé pour se connecter et obtenir un nouveau access token
.
J'utilise Axios pour tous mes appels d'API. J'ai un intercepteur configuré pour intercepter les réponses 401
.
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
})
Fondamentalement, lorsque j'intercepte une réponse 401, je souhaite effectuer une connexion puis réessayer la demande rejetée d'origine avec les nouveaux jetons. Ma fonction serviceRefreshLogin
appelle setAccessToken()
dans son bloc then
. Mais le problème est que le bloc then
se produit plus tard que le getAccessToken()
de l'intercepteur, de sorte que la nouvelle tentative a lieu avec les anciennes informations d'identification expirées.
getAccessToken()
et getRefreshToken()
renvoient simplement les jetons existants stockés dans le navigateur (ils gèrent le stockage local, les cookies, etc.).
Comment pourrais-je m'assurer que les déclarations ne sont pas exécutées jusqu'au retour d'une promesse?
(Voici un numéro correspondant sur github: https://github.com/mzabriskie/axios/issues/266 )
Il suffit d'utiliser une autre promesse: 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;
});
});
Si votre environnement ne supporte pas les promesses, utilisez polyfill, par exemple https://github.com/stefanpenner/es6-promise
Mais il peut être préférable de réécrire getRefreshToken pour retourner la promesse et simplifier ensuite le code.
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;
});
Cela pourrait se faire dans la demande au lieu de la réponse, et ce serait probablement plus propre, car cela éviterait de toucher le serveur lorsque le jeton d'accès a expiré. Copier depuis cet 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);
});