Dynamic web applications are very popular these days, and many web applications need to have some sort of user management to be able to best serve the app users.

One popular frontend and backend combination is using React (Javascript) on the frontend and Django (Python) on the backend.

There are many different ways to do user authentication in web applications, but I’d like to focus in this article on JWT Authentication.

JWT Authentication with Django is relatively easy with a handful of packages already out there.

The non-obvious piece comes with how do the browser and server interact to make sure that everything functions between Django and React.

One problem that sometimes will happen with various authentication packages is that an infinite loop will occur and a refresh token needs to be provided to the server for authentication.

Infinite loops are never good for an application, and in this post, I’m going to share with you how you can implement an interceptor using Axios in React to prevent infinite loops.

React Axios Interceptor to Prevent Infinite Loops in JWT Authentication

The following code is how you can prevent infinite loops in React using Axios.

On the backend, I’m utilizing rest_framework_simplejwt which provides us an access token (access_token) and refresh token (refresh_token).

When a user logs in, we receive an access token and refresh token from the server. Then, if the user goes to access some protected content, the server needs the access token to serve the requested data.

Sometimes the server throws a 401 unauthorized error when we are authorized to access that information – in this case, we need to include some extra code to ensure that we get the information. After receiving this 401 error, we then should check the refresh token and receive a new access token.

The problem comes in when the server continually keeps throwing 401 errors even after receiving new access tokens.

The key is to create a second axios instance which will handle the refresh request.

The code below should handle both of these problems, both the 401 unauthorized errors and the infinite loops.

If at the end, the user is unauthorized, this sends the user to ‘/’.

import axios from 'axios';

const baseURL =  "http://localhost:8000/api";

const AuthService = axios.create({
    baseURL: baseURL,
    timeout: 60000,
    headers: {
        'Authorization': localStorage.getItem('access_token') ? "JWT " + localStorage.getItem('access_token') : null,
    }
});

const RefreshService = axios.create({
    baseURL: baseURL,
    timeout: 10000
});

AuthService.interceptors.response.use(
    response => response,
    error => {
        const originalRequest = error.config;

        // Prevent infinite loops
        if (error.response.status === 401 && originalRequest.url === baseURL + '/token/refresh/') {
            window.location.href = '/';
            return Promise.reject(error);
        }

        if (error.response.data.code === "token_not_valid" &&
            error.response.status === 401 && 
            error.response.statusText === "Unauthorized") 
            {
                const refreshToken = localStorage.getItem('refresh_token');

                if (refreshToken){
                    const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));

                    // exp date in token is expressed in seconds, while now() returns milliseconds:
                    const now = Math.ceil(Date.now() / 1000);
                    console.log(tokenParts.exp);

                    if (tokenParts.exp > now) {
                        return AuthService.post('/token/refresh/', {refresh: refreshToken})
                        .then((response) => {
            
                            localStorage.setItem('access_token', response.data.access);
                            localStorage.setItem('refresh_token', response.data.refresh);
            
                            AuthService.defaults.headers['Authorization'] = "JWT " + response.data.access;
                            originalRequest.headers['Authorization'] = "JWT " + response.data.access;
            
                            return AuthService(originalRequest);
                        })
                        .catch(err => {
                            console.log(err)
                        });
                    } else {
                        console.log("Refresh token is expired", tokenParts.exp, now);
                        localStorage.removeItem('access_token');
                        localStorage.removeItem('refresh_token');
                        AuthService.defaults.headers['Authorization'] = null;
                        window.location.href = '/';
                    }
                } else{
                    console.log("Refresh token not available.")
                    window.location.href = '/';
                }
        }
      
      // specific error handling done elsewhere
      return Promise.reject(error);
  }
);

export default AuthService

I hope that this code snippet is helpful for you and helps you solve your infinite loop problems with JWT Authentication in your web app.

Categorized in:

JavaScript, React,

Last Update: February 26, 2024