Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Axios interceptors for token refreshing and more than 2 async requests available
let isRefreshing = false;
let refreshSubscribers = [];
const instance = axios.create({
baseURL: Config.API_URL,
});
instance.interceptors.response.use(response => {
return response;
}, error => {
const { config, response: { status } } = error;
const originalRequest = config;
if (status === 498) {
if (!isRefreshing) {
isRefreshing = true;
refreshAccessToken()
.then(newToken => {
isRefreshing = false;
onRrefreshed(newToken);
});
}
const retryOrigReq = new Promise((resolve, reject) => {
subscribeTokenRefresh(token => {
// replace the expired token and retry
originalRequest.headers['Authorization'] = 'Bearer ' + token;
resolve(axios(originalRequest));
});
});
return retryOrigReq;
} else {
return Promise.reject(error);
}
});
subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token));
}
@martinjeannot

This comment has been minimized.

Copy link

martinjeannot commented May 2, 2018

Not bad, although the refreshSubscribers array is never cleared so all pushed callbacks will run each time more than 2 async requests are made, also HTTP 498 is not widely used so I'd stick with the original 401, but otherwise it helped, thanks.

@FilipBartos

This comment has been minimized.

Copy link

FilipBartos commented May 22, 2018

Thank you for this gist. There is mine update using redux action for token refresh.

let isAlreadyFetchingAccessToken = false
let subscribers = []

function onAccessTokenFetched(access_token) {
  subscribers = subscribers.filter(callback => callback(access_token))
}

function addSubscriber(callback) {
  subscribers.push(callback)
}

axios.interceptors.response.use(function (response) {
  return response
}, function (error) {
  const { config, response: { status } } = error
  const originalRequest = config

  if (status === 401) {
    if (!isAlreadyFetchingAccessToken) {
      isAlreadyFetchingAccessToken = true
      store.dispatch(fetchAccessToken()).then((access_token) => {
        isAlreadyFetchingAccessToken = false
        onAccessTokenFetched(access_token)
      })
    }

    const retryOriginalRequest = new Promise((resolve) => {
      addSubscriber(access_token => {
        originalRequest.headers.Authorization = 'Bearer ' + access_token
        resolve(axios(originalRequest))
      })
    })
    return retryOriginalRequest
  }
  return Promise.reject(error)
})
@PauloRobertTlss

This comment has been minimized.

Copy link

PauloRobertTlss commented May 25, 2018

Parabéns! 10000 start!

@Sagaryalioe

This comment has been minimized.

Copy link

Sagaryalioe commented May 25, 2018

It means in your refreshAccessToken() function, it will ask for new access_token again for the next async request even if the access_token is not expired. Am I Correct? Since you are providing new refresh_token for next async request.

@drakoniprincessa

This comment has been minimized.

Copy link

drakoniprincessa commented Jun 22, 2018

at line 43 should be something like
refreshSubscribers = [];

@suchy

This comment has been minimized.

Copy link

suchy commented Sep 15, 2018

Hi! Could you share how did you access store from interceptors?

@ModPhoenix

This comment has been minimized.

Copy link

ModPhoenix commented Sep 19, 2018

@alfonmga

This comment has been minimized.

@Flyrell

This comment has been minimized.

Copy link

Flyrell commented Nov 23, 2018

Here's a small package I created for this one -> axios-auth-refresh.
I'd be more than glad to get your contributions, as it's pretty simple right now (it'd probably need to react on more status codes, queue the requests while the token obtaining process is running, etc.).

@marcelogarbin

This comment has been minimized.

Copy link

marcelogarbin commented Dec 10, 2018

Thank you for this gist. There is mine update using redux action for token refresh.

let isAlreadyFetchingAccessToken = false
let subscribers = []

function onAccessTokenFetched(access_token) {
  subscribers = subscribers.filter(callback => callback(access_token))
}

function addSubscriber(callback) {
  subscribers.push(callback)
}

axios.interceptors.response.use(function (response) {
  return response
}, function (error) {
  const { config, response: { status } } = error
  const originalRequest = config

  if (status === 401) {
    if (!isAlreadyFetchingAccessToken) {
      isAlreadyFetchingAccessToken = true
      store.dispatch(fetchAccessToken()).then((access_token) => {
        isAlreadyFetchingAccessToken = false
        onAccessTokenFetched(access_token)
      })
    }

    const retryOriginalRequest = new Promise((resolve) => {
      addSubscriber(access_token => {
        originalRequest.headers.Authorization = 'Bearer ' + access_token
        resolve(axios(originalRequest))
      })
    })
    return retryOriginalRequest
  }
  return Promise.reject(error)
})

Hello,
I used this solution and it worked, but I do not quite understand what it actually does when it calls the promise of the constant "retryOriginalRequest".

Could someone explain me how the code works?

thank you

@huhaotian

This comment has been minimized.

Copy link

huhaotian commented Dec 13, 2018

it works for me

thank you

@wrabit

This comment has been minimized.

Copy link

wrabit commented Jan 22, 2019

@marcelogarbin an axios interceptor should return a promise. He creates a promise retrying the original request and returns that. It's so you can use axios as intended, chaining .then() to carry out further actions.

@ognjetina

This comment has been minimized.

Copy link

ognjetina commented Jan 24, 2019

you are the Jaguar mate :)

@akhrabrov

This comment has been minimized.

Copy link

akhrabrov commented Apr 5, 2019

Thank u man!
I'm writed simple example for works with cookie/session in node.js

https://gist.github.com/nzvtrk/ebf494441e36200312faf82ce89de9f2

@garyoo

This comment has been minimized.

Copy link

garyoo commented May 23, 2019

Thank you very much

@daitonaaa

This comment has been minimized.

Copy link

daitonaaa commented Jun 22, 2019

Thank you man!!

@bionicvapourboy

This comment has been minimized.

Copy link

bionicvapourboy commented Jul 1, 2019

Using this interceptor, with 5 concurrent requests, I get 3 times a new access_token, I missed something ?

@oceanicdev

This comment has been minimized.

Copy link

oceanicdev commented Aug 15, 2019

@bionicvapourboy you can try to use a mutex for a promise creation.
https://www.npmjs.com/package/async-mutex

@ifier

This comment has been minimized.

Copy link

ifier commented Aug 22, 2019

Hope it is useful for someone. Here is mine:

import axios from 'axios';
import config from '../config';
import { refreshToken } from '../redux/actions/auth';

let isRefreshing = false;
let subscribers = [];

function onRefreshed({ authorisationToken }) {
  subscribers.map(cb => cb(authorisationToken));
}

function subscribeTokenRefresh(cb) {
  subscribers.push(cb);
}

const setupAxiosInterceptors = (tokens, dispatch) => {
  const request = axios.create({
    baseURL: config.apiUrl
  });

  request.interceptors.response.use(null, err => {
    const {
      config,
      response: { status }
    } = err;
    const originalRequest = config;

    if (status === 401) {
      if (!isRefreshing) {
        isRefreshing = true;
        dispatch(refreshToken(tokens)).then(newTokens => {
          isRefreshing = false;
          onRefreshed(newTokens);
          subscribers = [];
        });
      }
      return new Promise(resolve => {
        subscribeTokenRefresh(token => {
          originalRequest.headers.Authorization = `Bearer ${token}`;
          resolve(axios(originalRequest));
        });
      });
    }

    return Promise.reject(err);
  });

  return request;
};

export default setupAxiosInterceptors;

I'm using SSR (Next.js + React redux + redux-thunk).

PS: I have one major question - if I will move const request = axios.create({ baseURL: config.apiUrl }); out of the function (right before), my component that is connected to the Redux will not get refreshed token - WHY!? I've investigate this issue and my reducer is receiving new token:

REFRESH_USER_TOKEN_SUCCESS
eyJhbGciOiJSUzI1NiIsInB1ciI6IkFVVCIsInNpZyI6ImciLCJ0eXAi....................WbAQfSbIPhzbhqA
REFRESH_USER_TOKEN_SUCCESS

But my component will not (will get an old). Can somebody explain me?
If you use CSR - it will work for both cases (axios.create outside function as well).

@bionicvapourboy

This comment has been minimized.

Copy link

bionicvapourboy commented Aug 22, 2019

@bionicvapourboy you can try to use a mutex for a promise creation.
https://www.npmjs.com/package/async-mutex

Hi @oceanicdev, I've solved checking if the auth token has already changed compared to the original request. The issue is related to the grey zone where a call has already failed, and a new access token has already been requested although not avaliable.

@essramos

This comment has been minimized.

Copy link

essramos commented Sep 8, 2019

what happens to this if the refresh token call fails? Isn't that going to be an infinite loop of some sort?

@bionicvapourboy

This comment has been minimized.

Copy link

bionicvapourboy commented Sep 9, 2019

@essram

what happens to this if the refresh token call fails? Isn't that going to be an infinite loop of some sort?

Failing refresh token calls respond usually with 400 (or other codes), not 401 :

401: The request has not been applied because it lacks valid authentication credentials for the target resource.

In this snippet the request is managed as long the response is 401. Otherwise no loop is involved.
Errors other than 401 must be managed based on the app logic. Eg: Logout the user and bring him to the login page

@essramos

This comment has been minimized.

Copy link

essramos commented Sep 9, 2019

Ah that makes sense! Thank you very much! @bionic :)

@Abhi-Tech9

This comment has been minimized.

Copy link

Abhi-Tech9 commented Sep 15, 2019

Does any of above solution works for concurrent request ?

@ifier

This comment has been minimized.

Copy link

ifier commented Sep 16, 2019

Does any of above solution works for concurrent request ?

Yes. If token is invalid, all requests goes to the queue -> refreshing token -> again same requests.

@Abhi-Tech9

This comment has been minimized.

Copy link

Abhi-Tech9 commented Sep 16, 2019

Thanks,

So we already have mechanism to send response back to source method?

@Nonary

This comment has been minimized.

Copy link

Nonary commented Sep 25, 2019

@essramos

what happens to this if the refresh token call fails? Isn't that going to be an infinite loop of some sort?

As mentioned aboved, this technically would most likely not happen assuming you're using a token based authentication to where the browser can read the token and renew the token.

If however, you're using something like a corporate SSO that forces you to get tokens via an iframe (and http-only access) you would need to add an infinite loop guard.

You could easily do that by adding a property to the error.config itself, like retries... which iterates by 1 every attempt.
If the retries exceeds 3, return an error instead of returning the same request.

@charanjit-singh

This comment has been minimized.

Copy link

charanjit-singh commented Oct 5, 2019

Use Semaphores instead of a variable

@iwan-uschka

This comment has been minimized.

Copy link

iwan-uschka commented Oct 10, 2019

I really like this approach: khinkali/cockpit#3 (comment)

@jeffceriello

This comment has been minimized.

Copy link

jeffceriello commented Oct 16, 2019

Hi, I'm pretty new to React. I am building a RN app, trying to persist the authentication and refresh the token when needed/at every request. Where should this code be added and called?

@iwan-uschka

This comment has been minimized.

Copy link

iwan-uschka commented Oct 16, 2019

Have a look at this gist...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.