I’ve published a few posts on authentication in the last year or so because I feel it’s one of those areas of web development that can be approached in so many ways depending on requirements and developer experience. Some of my authentication posts have been centered around JSON Web Token authentication (JWT) with Redux and Node for which the code is available on Github, but this post will expand on a system I’ve developed for refreshing a users JWT access token, specifically by using Redux middleware.

If you want to understand the full auth process or even what JSON Web Tokens do, go ahead and read the links above for a start, or this great post from Auth0. For a brief overview though, see below.

Overview of JWT auth process

When the user logs in, they have an auth token and refresh token generated. The auth tokens generally last a short amount of time - I prefer to have them around the time of a standard session on your application. The token is then sent along with each request to the server in an authorization header. Once the auth token expires, the refresh token is used to generate the user a new auth token.

As additional “payload” data can be stored in JWT’s (such as user ID’s, permissions, user’s name etc.), this method has the benefit of not having to check against a database to do user checks or retrieve any additional user information which may be stored in the JWT.

As I mentioned earlier, there are many ways to handle user auth - using JWT’s, sessions, cookies etc., so be sure to read around the breadth of the topic if you’re interested. Using JWT’s raises some concerns over security and may not doesn’t fit all use cases, so keep that in mind.

How Redux assists with this process

I still love using Redux, even with this seemingly mass exodus of people moving over to local state management with systems built in Apollo or using the Context API in React core. Redux’s middleware functionality is absolutely perfect for handling all sorts of authentication logic, especially for handling the refreshing of JWT auth tokens. Here’s a quick list of steps that occur with my Redux-based system:

  • The user receives an auth token and a refresh token.
  • The user performs requests to the server, sending the JWT in the Authorization header with each request.
  • The user’s auth token eventually expires (say after 5 minutes), and the expired token is sent to the server.
  • The server returns a 403 error, which tells the front-end application that the JWT has expired, and this is distributed to Redux middleware.
  • The Redux middleware dispatches an action creator to refresh the token.
  • While this is happening, the previous server request that was not successful is stored in a “buffer” array.
  • Once the token is refreshed, the middleware looks in the buffer array, and fires off the last request which was not successful.

The key here is the last point. It’s a walk in the park to refresh an auth token as it requires sending a simple additional server request, but the end goal is to have the user’s initial request sent again with the new auth token.

The JWT refresh middleware

Following on from the list above, here’s the middleware:

import {
  logout,
  refreshToken
} from '../auth/auth.service';

let buffer = []; // create an empty array which will be populated with all actions dispatched by Redux

export const jwt = store => next => action => {
  buffer.push(action);
  if (action.type === 'INVALID_TOKEN') {
    let theStore = store.getState();
    if (
      theStore.auth &&
      theStore.auth.authToken &&
      theStore.auth.refreshToken
    ) {
      if (!theStore.auth.pendingRefreshingToken) {
        store.dispatch({
          type: 'REFRESHING_TOKEN'
        });
        store.dispatch(refreshToken(theStore.auth.refreshToken)).then(() => {
          store.dispatch({
            type: 'TOKEN_REFRESHED'
          });

          //get the action before the last INVALID_TOKEN (the one which got denied because of token expiration)
          let pos = buffer.map(e => e.type).indexOf('INVALID_TOKEN') - 1;

          // count back from the invalid token dispatch, and fire off the last dispatch again which was
          // a function. These are to be dispatched, and have the dispatch function passed through to them.
          for (var i = pos; i > -1; i -= 1) {
            if (typeof buffer[i] === 'function') {
              store.dispatch({
                type: 'RESEND',
                action: buffer[i](store.dispatch)
              });
              break;
            }
          }
          buffer = [];
        });
      }
    }
  } else if (action.type === 'REFRESH_EXPIRED') {
    buffer = [];
    store.dispatch(logout());
  } else {
    if (buffer.length > 20) {
      //remove all items but keep the last 20 which forms the buffer, else the array would keep growing
      buffer.splice(0, buffer.length - 20);
    }
    return next(action);
  }
};

The buffer is always limited to 20 array items, which is spliced after each dispatched action. While the token is refreshing, the buffer is being populated with Redux actions. The refreshing of the auth token is an asynchronous process, so once this is completed/resolved, the buffer is searched for the failed action which will always be immediately before the INVALID_TOKEN action, and this failed action is re-sent.

It’s worth noting that this process can be handled on the server by automatically refreshing the token along with the initial request and sending back the originally request response along with the new auth token. This middleware is by no means the perfect or most efficient solution to refreshing an auth token, but it’s one I’ve developed over a few iterations of my JWT & React Native Boilerplate. I’ve recently updated this boilerplate with the most up-to-date version of the middleware mentioned in this post, so to see to whole refresh process in action feel free to pull that down and give it a spin.

Also let me know if you have any suggestions or would like to submit a PR to update the boilerplate, as it’s always good to hear a different perspective. Thanks for reading.