Implementing Automatic Token Refresh in React

Introduction

In today’s modern web applications, security is of utmost importance, especially when it comes to user authentication and authorization. One common method of managing user sessions is through the use of JSON Web Tokens (JWT). These tokens allow servers to verify users without constantly querying a database. However, JWTs have a set expiration time, which means that users can find themselves logged out unexpectedly. This is where automatic token refresh comes into play, ensuring a smooth user experience without compromising security.

In this article, we’ll delve into implementing an automatic token refresh mechanism in a React application. We will explore why you need token refresh, how to effectively manage JWTs, and step-by-step instructions to build a seamless experience for your users. Whether you’re a beginner or an experienced developer, this guide will equip you with the knowledge to handle token refresh robustly.

Before we get into the code, let’s understand the fundamentals of how JWTs work and what happens when they expire. A JWT contains three parts – the header, the payload, and the signature. The payload includes important information such as user details and expiration time. Once this token is generated, you send it to the client, which in turn provides it back with each request. However, after the token expires, a user must re-authenticate, which can be disruptive. Automatic token refresh allows us to keep the session alive without prompting the user for re-login.

Understanding JWT Expiration

JWTs typically come with an expiration time embedded in the payload. For instance, consider a token that expires in one hour. Once this hour elapses, any requests made with that token will receive a 401 Unauthorized response from the server. This is the moment when user experience is compromised. Users cannot engage with the application, and the application must manage the error gracefully.

The server might provide a refresh token that can be used to obtain a new access token without requiring the user to log back in. This refresh token usually has a longer lifespan than the access token, allowing users to remain authenticated as long as they have it. The trick is knowing when to utilize this refresh token. For most applications, you’ll want to set up a mechanism that checks the expiration status of the access token before it makes a request.

In our React application, we will use a combination of state management (like Redux or Context API), hooks, and API calls to manage the token lifecycle effectively. This setup ensures that our application can gracefully renew access tokens in the background, keeping the user Session fluid and uninterrupted.

Setting Up a React Application

Firstly, let’s set up a new React application if you don’t have one already. You can quickly bootstrap a React application using create-react-app:

npx create-react-app token-refresh-example

Once your application is up and running, ensure you have an API that supports JWTs. If you don’t have a backend yet, you can use tools like json-server to mock an API or set up a simple Node.js server with Express to generate tokens.

Next, you will need the following dependencies installed in your React application:

npm install axios jwt-decode

Here, axios will be used for making API calls, and jwt-decode will help decode the JWT to check its expiration date conveniently.

Creating a Token Management Utility

Before diving into the components, let’s create a utility function to manage our tokens. Create a new file under src/utils named tokenService.js and implement the following functions:

import jwtDecode from 'jwt-decode';

const TOKEN_KEY = 'jwt_token';
const REFRESH_TOKEN_KEY = 'refresh_token';

export const saveToken = (token, refreshToken) => {
  localStorage.setItem(TOKEN_KEY, token);
  localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
};

export const getToken = () => localStorage.getItem(TOKEN_KEY);

export const getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);

export const isTokenExpired = (token) => {
  if (!token) return true;
  const decoded = jwtDecode(token);
  return decoded.exp * 1000 < Date.now();
};

export const clearTokens = () => {
  localStorage.removeItem(TOKEN_KEY);
  localStorage.removeItem(REFRESH_TOKEN_KEY);
};

This utility will help save, retrieve, and manage the JWT and refresh token. Make sure to call saveToken function upon successful login and store the tokens in local storage for access during your session.

Handling Token Refresh

Now, let’s handle token refresh whenever a user interacts with the application. The first step is to set up an Axios interceptor. This allows us to automatically check if the token is about to expire before making an API request. If it is, we can first refresh the token and then proceed with the original request.

Create a file called axiosConfig.js in your src/utils folder, and implement the interceptor as follows:

import axios from 'axios';
import { getToken, getRefreshToken, isTokenExpired, saveToken, clearTokens } from './tokenService';

const axiosInstance = axios.create({
  baseURL: 'http://yourapi.com/api', // Replace with your API base URL
});

axiosInstance.interceptors.request.use(async (config) => {
  const token = getToken();
  const refreshToken = getRefreshToken();

  if (isTokenExpired(token) && refreshToken) {
    // Attempt to refresh the token
    try {
      const response = await axios.post('/auth/refresh', { refreshToken });
      const { token: newToken, refreshToken: newRefreshToken } = response.data;
      saveToken(newToken, newRefreshToken);
      config.headers['Authorization'] = `Bearer ${newToken}`;
    } catch (error) {
      console.error('Refresh token failed', error);
      clearTokens();
      // Optionally redirect the user to login
    }
  } else {
    config.headers['Authorization'] = `Bearer ${token}`;
  }
  return config;
});

export default axiosInstance;

In this code snippet, we set up an instance of Axios with an interceptor that checks if the access token is expired. If it is and a refresh token exists, it sends a request to refresh the token. Upon successful retrieval of a new token, we update storage and proceed with the original request. This clever utilization of Axios allows for automatic token management with minimal disruption to the user.

Creating the Authentication Components

Next, let’s create the authentication component to handle login functionality. Start with a simple login form in a new component. Create a file called Login.js and implement the form as shown:

import { useState } from 'react';
import axios from './utils/axiosConfig';
import { saveToken } from './utils/tokenService';

const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (event) => {
    event.preventDefault();
    try {
      const response = await axios.post('/auth/login', { email, password });
      const { token, refreshToken } = response.data;
      saveToken(token, refreshToken);
      // Redirect or update UI as needed
    } catch (error) {
      console.error('Login failed', error);
    }
  };

  return (
    
setEmail(e.target.value)} required /> setPassword(e.target.value)} required />
); }; export default Login;

This component manages user input through controlled components and sends authentication details to the backend. Upon successful login, it saves the tokens and provides the necessary feedback. You can add token expiry checks as needed depending on your application’s requirements.

Ensuring Token Refresh Throughout the Application

To maintain a consistent user experience, you’ll want your application to be aware of the login status and token validity across different components. This can be achieved using React’s Context API or state management libraries like Redux.

For simplicity, let’s use Context. Create a new Context provider that wraps your application. This provider will manage authentication state and provide necessary values to child components. Create a new file named AuthContext.js in your src/context directory:

import React, { createContext, useContext, useState, useEffect } from 'react';
import { getToken, clearTokens } from './utils/tokenService';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    const token = getToken();
    if (token) { 
      setIsAuthenticated(true);
    } else {
      clearTokens();
    }
  }, []);

  return (
    
      {children}
    
  );
};

export const useAuth = () => useContext(AuthContext);

The AuthProvider sets the authentication state based on the existence of a valid token. You can now wrap your main application in this provider to give access to authentication status throughout your application.

Conclusion

Implementing automatic token refresh in a React application is vital for creating a seamless and user-friendly experience. By employing a systematic approach using Axios interceptors and React Context API, developers can ensure that their applications not only manage JWTs effectively but also handle authentication-related challenges gracefully. Passing in the tokens at the right times, checking for expiration, and refreshing efficiently shield the users from authentication bumps.

The tutorial provided a solid foundation for automatic token refresh mechanics using React, and with this framework in place, you can further enhance it with features such as 2FA, rotating refresh tokens, or role-based access management. The key to robust web applications lies in balancing convenience with security, and while you’re building your skills in this space, remember to keep exploring, experimenting, and sharing your journey through the developer community.

So gear up and take your web development to the next level by mastering token refresh mechanisms in your React applications. Happy coding!

Scroll to Top