Our React Native Redux app uses JWT tokens for authentication. There are many actions that require such tokens and a lot of them are dispatched simultaneously e.g. when app loads.
E.g.
componentDidMount() {
dispath(loadProfile());
dispatch(loadAssets());
...
}
Both loadProfile
and loadAssets
require JWT. We save the token in the state and AsyncStorage
. My question is how to handle token expiration.
Originally I was going to use middleware for handling token expiration
// jwt-middleware.js
export function refreshJWTToken({ dispatch, getState }) {
return (next) => (action) => {
if (isExpired(getState().auth.token)) {
return dispatch(refreshToken())
.then(() => next(action))
.catch(e => console.log('error refreshing token', e));
}
return next(action);
};
}
The problem that I ran into was that refreshing of the token will happen for both loadProfile
and loadAssets
actions because at the time when they are dispatch the token will be expired. Ideally I would like to "pause" actions that require authentication until the token is refreshed. Is there a way to do that with middleware?
I made a simple wrapper around
redux-api-middleware
to postpone actions and refresh access token.middleware.js
I keep tokens in the state, and use a simple helper to inject Acess token into a request headers
So
redux-api-middleware
actions stays almost unchangedI wrote the article and shared the project example, that shows JWT refresh token workflow in action
I found a way to solve this. I am not sure if this is best practice approach and there are probably some improvements that could be made to it.
My original idea stays: JWT refresh is in the middleware. That middleware has to come before
thunk
ifthunk
is used.Then in the middleware code we check to see if token is expired before any async action. If it is expired we also check if we are already are refreshing the token -- to be able to have such check we add promise for fresh token to the state.
The most important part is
refreshToken
function. That function needs to dispatch action when token is being refreshed so that the state will contain the promise for the fresh token. That way if we dispatch multiple async actions that use token auth simultaneously the token gets refreshed only once.I realize that this is pretty complicated. I am also a bit worried about dispatching actions in
refreshToken
which is not an action itself. Please let me know of any other approach you know that handles expiring JWT token with redux.Instead of "waiting" for an action to finish, you could instead keep a store variable to know if you're still fetching tokens:
Sample reducer
Now the action creator:
This gets called when the component mounted. If the auth key is stale, it will dispatch an action to set
fetching
to true and also refresh the token. Notice that we aren't going to load the profile or assets yet.New component:
Notice that now you attempt to load your things on mount but also under certain conditions when receiving props (this will get called when the store changes so we can keep
fetching
there) When the initial fetch fails, it will trigger therefreshToken
. When that is done, it'll set the new token in the store, updating the component and hence callingcomponentWillReceiveProps
. If it's not still fetching (not sure this check is necessary), it will load things.