Axios interceptors and asynchronous login

57,459

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);
});
Share:
57,459

Related videos on Youtube

Dmitry Shvedov
Author by

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, 2020

Comments

  • Dmitry Shvedov
    Dmitry Shvedov about 4 years

    I'm implementing token authentication in my web app. My access token expires every N minutes and than a refresh token is used to log in and get a new access 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 calls setAccessToken() in its then block. But the problem is that the then block happens later than the getAccessToken() in the interceptor, so the retry happens with the old expired credentials.

    getAccessToken() and getRefreshToken() 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
    Dmitry Shvedov about 8 years
    Unfortunately that doesn't work as an interceptor. When I try this, the request gets retried only AFTER the original promise gets rejected.
  • Dmitry Shvedov
    Dmitry Shvedov about 8 years
    Sorry, 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
    Rayjax about 7 years
    just a quick note, for me err.status is undefined, used err.response.status instead