redux-axios-middleware
redux-axios-middleware copied to clipboard
How to refresh expired access tokens before submitting the original request?
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...
- check the token expiration in the request interceptor
- retrieve a new access token with the help of a refresh token before
- 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
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);
};
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)
}]
},
},
},
};