web-dev-qa-db-fra.com

Intercepteurs Axios et connexion asynchrone

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 )

34
Dmitry Shvedov

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;
});

Démo https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

24
ForceUser

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);
});
7
Daniel