svrcekmichal / redux-axios-middleware

Redux middleware for fetching data with axios HTTP client
MIT License
922 stars 96 forks source link

How to refresh expired access tokens before submitting the original request? #87

Open johannesschirrmeister opened 5 years ago

johannesschirrmeister commented 5 years ago

Hi,

Thank you for this great library! I'm using an interceptor to add JWT access tokens to the requests in order to access our protected APIs. Now I'm trying to figure out what's the best way to deal with expired access tokens. Ideally, I would like to...

  1. check the token expiration in the request interceptor
  2. retrieve a new access token with the help of a refresh token before
  3. submitting the original request

Could someone point me in the right direction? Is this generally feasible or should I rather focus on handling the 401 responses caused by the expired token? If it can be done, how would I go about it?

Asking here as I think it may be relevant for others.

Thank you, Johannes

johannesschirrmeister commented 5 years ago

I still wanted to follow up with the solution I eventually went for. Hope it will be useful for someone. And, of course, please critique!

I created a custom middleware that runs before this (redux-axios-middleware) middleware. It basically intercepts every request and checks if the access token is still valid. If it's not, it will refresh the access token and make all other incoming requests wait. As I'm writing this, I realize it's probaby not super clean to release the blocked requests in random order, but it should be good enough for my use case. Here is the middleware code:

const isRequest = (action) => {
    return action.payload && action.payload.request;
};

export default store => next => async action => {
    if (isRequest(action) && action.type !== GET_REFRESH_TOKEN) {
        if (isExpired(store.getState().tokensReducer.accessTokenExpiresAt)) {
            if (!store.getState().tokensReducer.refreshingToken) {
                try {
                    await store.dispatch(refreshToken());
                } catch (error) {
                    console.log('Failed to refresh access token. ' + error)
                }
            } else {
                let maxRetries = 5;
                for(let i = 0; i < maxRetries; i++) {
                    await sleep(100);
                    if (!store.getState().tokensReducer.refreshingToken) {
                        break;
                    }
                }
            }
        }
    }
    return next(action);
};
matviishyn commented 4 years ago

I ended up with something like that

const expiredAccessInterceptor = ({dispatch}, error) => {
  const statusCode = get(error, 'response.status', null); // get from lodash

  if (statusCode === 401) {
    // dispatch logout action
    return Promise.reject((error));
  }
};

export const client = {
  default: {
    client: axios.create({
      baseURL: APP_API_URL,
      responseType: 'json',
    }),
    options: {
      interceptors: {
        request: [tokenInterceptor],
        response: [{
          error: (store, error) => expiredAccessInterceptor(store, error)
        }]
      },
    },
  },
};