Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Redux to refresh JWT token?

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?

like image 466
lanan Avatar asked Apr 29 '16 22:04

lanan


People also ask

Can we refresh JWT Token?

This allows you to have short-lived access tokens without having to collect credentials every time one expires. Since access tokens aren't valid for an extended period because of security reasons, a refresh token helps to re-authenticate a user without login credentials.

How do you use refresh tokens in JWT?

For the refresh token, we will simply generate a UID and store it in an object in memory along with the associated user username. It would be normal to save it in a database with the user's information and the creation and expiration date (if we want it to be valid for a limited period of time).


1 Answers

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.

like image 199
lanan Avatar answered Sep 22 '22 08:09

lanan