React + Redux: Refresh Token with Axios and JWT example

With previous posts, we’ve known how to build Authentication and Authorization in a React Redux Application. In this tutorial, I will continue to show you way to implement Redux Refresh Token with Axios Interceptors and JWT.

Related Posts:
Axios Interceptors tutorial with Refresh Token example
In-depth Introduction to JWT-JSON Web Token
React + Redux: JWT Authentication example
React Hooks + Redux: JWT Authentication example

Without Redux: React Refresh Token with JWT and Axios Interceptors


React Redux Refresh Token with Axios overview

The diagram shows flow of how we implement React Redux JWT Refresh Token with Axios.

react-redux-refresh-token-axios-jwt-flow

– A refreshToken will be provided at the time user signs in.
– A legal JWT must be added to HTTP Header if Client accesses protected resources.
– With the help of Axios Interceptors, React App can check if the accessToken (JWT) is expired (401), sends /refreshToken request to receive new accessToken and use it for new resource request.

Let’s see how the React Redux Refresh Token works with demo UI.

– User makes an account login first.

react-redux-refresh-token-axios-jwt-login

– Now user can access resources with available Access Token.

react-redux-refresh-token-axios-jwt-access-resources-ui

react-redux-refresh-token-axios-jwt-access-resources

– When the Access Token is expired, React automatically send Refresh Token request, receive new Access Token and use it with new request.

react-redux-refresh-token-axios-jwt-access-token-expired

react-redux-refresh-token-axios-jwt-refresh-token

react-redux-refresh-token-axios-jwt-new-access-token

– After a period of time, the new Access Token is expired again, and the Refresh Token too.

react-redux-refresh-token-axios-jwt-new-access-token-expired

react-redux-refresh-token-axios-jwt-refresh-token-expired

Now the user cannot access restricted resources.

react-redux-refresh-token-axios-jwt-refresh-token-expired-ui

The Back-end server for this React Client can be found at:

We’re gonna implement Token Refresh feature basing on the code from previous posts, so you need to read one of following tutorials first:

Redux with Refresh Token

Now we need to add a Redux action – REFRESH_TOKEN.

actions/types.js

...
export const REFRESH_TOKEN = "REFRESH_TOKEN";
...

actions/auth.js

import {
  ...
  REFRESH_TOKEN
} from "./types";

import AuthService from "../services/auth.service";

export const register = ...;

export const login = ...;

export const logout = ...;

export const refreshToken = (accessToken) => (dispatch) => {
  dispatch({
    type: REFRESH_TOKEN,
    payload: accessToken,
  })
}

And modify reducer with new type – REFRESH_TOKEN.

reducers/auth.js

...
import {
  ...
  REFRESH_TOKEN
} from "../actions/types";

const user = JSON.parse(localStorage.getItem("user"));

const initialState = user
  ? { isLoggedIn: true, user }
  : { isLoggedIn: false, user: null };

export default function (state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    ...
    case REFRESH_TOKEN:
      return {
        ...state,
        user: { ...user, accessToken: payload },
      };
    default:
      return state;
  }
}

Axios interceptors in React Redux App

Let’s create a service that provides an Axios instance.

services/api.js

import axios from "axios";

const instance = axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-Type": "application/json",
  },
});

export default instance;

Then we’re gonna create a setupInterceptors() function with interceptors request and dispatch the Redux action for refresh token in the response.

services/setupInterceptors.js

import axiosInstance from "./api";
import TokenService from "./token.service";
import { refreshToken } from "../actions/auth";

const setup = (store) => {
  axiosInstance.interceptors.request.use(
    (config) => {
      const token = TokenService.getLocalAccessToken();
      if (token) {
        // config.headers["Authorization"] = 'Bearer ' + token;  // for Spring Boot back-end
        config.headers["x-access-token"] = token; // for Node.js Express back-end
      }
      return config;
    },
    (error) => {
      return Promise.reject(error);
    }
  );

  const { dispatch } = store;
  axiosInstance.interceptors.response.use(
    (res) => {
      return res;
    },
    async (err) => {
      const originalConfig = err.config;

      if (originalConfig.url !== "/auth/signin" && err.response) {
        // Access Token was expired
        if (err.response.status === 401 && !originalConfig._retry) {
          originalConfig._retry = true;

          try {
            const rs = await axiosInstance.post("/auth/refreshtoken", {
              refreshToken: TokenService.getLocalRefreshToken(),
            });

            const { accessToken } = rs.data;

            dispatch(refreshToken(accessToken));
            TokenService.updateLocalAccessToken(accessToken);

            return axiosInstance(originalConfig);
          } catch (_error) {
            return Promise.reject(_error);
          }
        }
      }

      return Promise.reject(err);
    }
  );
};

export default setup;

In the code above, we:
– intercept requests or responses before they are handled by then or catch.
– handle 401 status on interceptor response (without response of login request).
– use a flag call _retry on original Request (config) to handle Infinite loop. It is the case that request is failed again, and the server continue to return 401 status code.

For more details, please visit:
Axios Interceptors tutorial with Refresh Token example

Let’s import the setupInterceptors() function in index.js where we can pass the Redux store.

src/index.js

...
import store from "./store";
import setupInterceptors from "./services/setupInterceptors";
...

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

setupInterceptors(store);
...

Update Services with new Axios Interceptors

Axios Interceptors manipulate the header, body, parameters of the requests sent to the server so that we don’t need to add headers in Axios requests like this:

axios.get(API_URL, { headers: authHeader() })

So we remove auth-header.js file, then update services that use it with new api service.

services/user.service.js

import api from './api';

class UserService {
  getPublicContent() {
    return api.get('/test/all');
  }

  getUserBoard() {
    return api.get('/test/user');
  }

  getModeratorBoard() {
    return api.get('/test/mod');
  }

  getAdminBoard() {
    return api.get('/test/admin');
  }
}

export default new UserService();

services/auth.service.js

import api from "./api";
import TokenService from "./token.service";

class AuthService {
  login(username, password) {
    return api
      .post("/auth/signin", {
        username,
        password
      })
      .then(response => {
        if (response.data.accessToken) {
          TokenService.setUser(response.data);
        }

        return response.data;
      });
  }

  logout() {
    TokenService.removeUser();
  }

  register(username, email, password) {
    return api.post("/auth/signup", {
      username,
      email,
      password
    });
  }
}

export default new AuthService();

We also need to create TokenService which Axios instance and other services use above.
TokenService provides get, set, remove methods to work with Token and User Data stored on Browser.

services/token.service.js

class TokenService {
  getLocalRefreshToken() {
    const user = JSON.parse(localStorage.getItem("user"));
    return user?.refreshToken;
  }

  getLocalAccessToken() {
    const user = JSON.parse(localStorage.getItem("user"));
    return user?.accessToken;
  }

  updateLocalAccessToken(token) {
    let user = JSON.parse(localStorage.getItem("user"));
    user.accessToken = token;
    localStorage.setItem("user", JSON.stringify(user));
  }

  getUser() {
    return JSON.parse(localStorage.getItem("user"));
  }

  setUser(user) {
    console.log(JSON.stringify(user));
    localStorage.setItem("user", JSON.stringify(user));
  }

  removeUser() {
    localStorage.removeItem("user");
  }
}

export default new TokenService();

React Hooks + Redux: Refresh Token

If you use code in React Hooks + Redux: JWT Authentication example, you can modify services like this.

services/user.service.js

import api from "./api";

const getPublicContent = () => {
  return api.get("/test/all");
};

const getUserBoard = () => {
  return api.get("/test/user");
};

const getModeratorBoard = () => {
  return api.get("/test/mod");
};

const getAdminBoard = () => {
  return api.get("/test/admin");
};

const UserService = {
  getPublicContent,
  getUserBoard,
  getModeratorBoard,
  getAdminBoard,
};

export default UserService;

services/auth.service.js

import api from "./api";
import TokenService from "./token.service";

const register = (username, email, password) => {
  return api.post("/auth/signup", {
    username,
    email,
    password
  });
};

const login = (username, password) => {
  return api
    .post("/auth/signin", {
      username,
      password
    })
    .then((response) => {
      if (response.data.accessToken) {
        TokenService.setUser(response.data);
      }

      return response.data;
    });
};

const logout = () => {
  TokenService.removeUser();
};

const getCurrentUser = () => {
  return JSON.parse(localStorage.getItem("user"));
};

const AuthService = {
  register,
  login,
  logout,
  getCurrentUser,
};

export default AuthService;

services/token.service.js

const getLocalRefreshToken = () => {
  const user = JSON.parse(localStorage.getItem("user"));
  return user?.refreshToken;
};

const getLocalAccessToken = () => {
  const user = JSON.parse(localStorage.getItem("user"));
  return user?.accessToken;
};

const updateLocalAccessToken = (token) => {
  let user = JSON.parse(localStorage.getItem("user"));
  user.accessToken = token;
  localStorage.setItem("user", JSON.stringify(user));
};

const getUser = () => {
  return JSON.parse(localStorage.getItem("user"));
};

const setUser = (user) => {
  console.log(JSON.stringify(user));
  localStorage.setItem("user", JSON.stringify(user));
};

const removeUser = () => {
  localStorage.removeItem("user");
};

const TokenService = {
  getLocalRefreshToken,
  getLocalAccessToken,
  updateLocalAccessToken,
  getUser,
  setUser,
  removeUser,
};

export default TokenService;

Conclusion

Today we know how to implement JWT Refresh Token into a React Redux Application using Axios Interceptors. For your understanding the logic flow, you should read one of following tutorials first:

The Back-end server for this React Client can be found at:

Further Reading

Related Posts:
In-depth Introduction to JWT-JSON Web Token
Axios Interceptors tutorial with Refresh Token example

Fullstack Authentication & Authorization:
React + Spring Boot
React + Node.js Express

You can simplify import statement with:
Absolute Import in React

Source Code

The source code for this React Application can be found at Github.

Without Redux: React Refresh Token with JWT and Axios Interceptors

4 thoughts to “React + Redux: Refresh Token with Axios and JWT example”

  1. Oh, just figured it out!

    I was setting the middleware after my , just put it before and it worked.

    I think I just need to send this question lol.

    Tks

  2. Hi! Very nice example, I’m using it.

    Maybe I missed something, because my interceptors are being triggered only when I click over a link, never when refreshing browser.

    Have you seen something like this?

    I’m reviewing my code… but thanks for that!

Comments are closed to reduce spam. If you have any question, please send me an email.