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
if thunk
is used.
...
const createStoreWithMiddleware = applyMiddleware(jwt, thunk)(createStore);
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.
import { refreshToken } from '../actions/auth';
export function jwt({ dispatch, getState }) {
return (next) => (action) => {
// only worry about expiring token for async actions
if (typeof action === 'function') {
if (getState().auth && getState().auth.token) {
// decode jwt so that we know if and when it expires
var tokenExpiration = jwtDecode(getState().auth.token).<your field for expiration>;
if (tokenExpiration && (moment(tokenExpiration) - moment(Date.now()) < 5000)) {
// make sure we are not already refreshing the token
if (!getState().auth.freshTokenPromise) {
return refreshToken(dispatch).then(() => next(action));
} else {
return getState().auth.freshTokenPromise.then(() => next(action));
}
}
}
}
return next(action);
};
}
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.
export function refreshToken(dispatch) {
var freshTokenPromise = fetchJWTToken()
.then(t => {
dispatch({
type: DONE_REFRESHING_TOKEN
});
dispatch(saveAppToken(t.token));
return t.token ? Promise.resolve(t.token) : Promise.reject({
message: 'could not refresh token'
});
})
.catch(e => {
console.log('error refreshing token', e);
dispatch({
type: DONE_REFRESHING_TOKEN
});
return Promise.reject(e);
});
dispatch({
type: REFRESHING_TOKEN,
// we want to keep track of token promise in the state so that we don't try to refresh
// the token again while refreshing is in process
freshTokenPromise
});
return freshTokenPromise;
}
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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…