web-dev-qa-db-ja.com

Axiosインターセプターと非同期ログイン

Webアプリにトークン認証を実装しています。 _access token_はN分ごとに期限切れになり、_refresh token_を使用してログインし、新しい_access token_を取得します。

すべてのAPI呼び出しにAxiosを使用しています。 _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
})
_

基本的に、401応答をインターセプトする際に、ログインを行い、拒否された元の要求を新しいトークンで再試行します。 serviceRefreshLogin関数は、thenブロックでsetAccessToken()を呼び出します。しかし問題は、インターセプターのthenブロックがgetAccessToken()よりも遅く発生するため、古い期限切れの資格情報で再試行が行われることです。

getAccessToken()およびgetRefreshToken()は、ブラウザに保存されている既存のトークンを返します(localStorage、Cookieなどを管理します)。

約束が戻るまで文が実行されないようにするにはどうすればよいですか?

(githubの対応する問題は次のとおりです。 https://github.com/mzabriskie/axios/issues/266

34
Dmitry Shvedov

別の約束を使用してください: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;
    });
});

環境が約束をサポートしていない場合、ポリフィルを使用します。たとえば、 https://github.com/stefanpenner/es6-promise

ただし、getRefreshTokenを書き換えてpromiseを返し、コードを単純にする方が良い場合があります

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

デモ https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

24
ForceUser

応答の代わりに要求でそれを行うことができます。アクセストークンの有効期限が切れたときにサーバーにヒットすることを避けるため、おそらくよりクリーンになります。 この記事 からのコピー:

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