top of page
Search

Streamlining JWT Handling in React with Axios Interceptors and a Custom Instance (TypeScript)

## Streamlining JWT Handling in React with Axios Interceptors and a Custom Instance (TypeScript)


Managing JSON Web Tokens (JWTs) in a React frontend interacting with a Spring Boot backend can be tricky, especially when dealing with token expiration and refresh mechanisms. A common challenge is handling 403 (Forbidden) errors when access tokens expire during user sessions. This article demonstrates a robust approach using Axios interceptors within a custom Axios instance, all written in TypeScript, to seamlessly manage JWTs and refresh tokens in your React application.


### The Problem: Expired Tokens and 403 Errors


When your React application makes requests to protected endpoints on your Spring Boot backend, it includes the JWT in the `Authorization` header. If the access token has expired, the backend will return a 403 error. A naive approach would be to handle this error on a case-by-case basis within your components, leading to repetitive and error-prone code. A more elegant solution is to use Axios interceptors.


### The Solution: Axios Interceptors and a Custom Instance (TypeScript)


Axios provides interceptors, which are functions that can intercept requests and responses before they are handled by your application code. We'll leverage these, within a custom Axios instance, to create a centralized mechanism for managing JWTs and refreshing tokens.  TypeScript will add type safety and improve code maintainability.


1. Creating a Custom Axios Instance (TypeScript):


```typescript

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';


const api: AxiosInstance = axios.create({

  baseURL: 'YOUR_API_BASE_URL', // Replace with your Spring Boot API URL

});


// ... (Interceptor code will go here)


export default api;

```


This creates a typed Axios instance (`api`) with your base URL. All requests made using this instance will automatically use this base URL.


2. Implementing the Refresh Token Logic (TypeScript):


```typescript

interface Tokens {

  accessToken: string | null;

  refreshToken: string | null;

}


const getTokens = (): Tokens => {

  const accessToken = localStorage.getItem('accessToken');

  const refreshToken = localStorage.getItem('refreshToken');

  return { accessToken, refreshToken };

};


const refreshAccessToken = async (): Promise<string | null> => {

  try {

    const { refreshToken } = getTokens();


    if (!refreshToken) {

      console.error("No refresh token available. Redirecting to login.");

      // Handle missing refresh token (e.g., redirect to login)

      return null;

    }


    const refreshResponse: AxiosResponse<{ accessToken: string }> = await api.post('/refresh_token', { refreshToken }); // Your refresh endpoint

    const newAccessToken = refreshResponse.data.accessToken;


    localStorage.setItem('accessToken', newAccessToken); // Update local storage

    return newAccessToken;


  } catch (error) {

    console.error("Error refreshing token:", error);

    localStorage.removeItem('accessToken');

    localStorage.removeItem('refreshToken');

    // Handle refresh token failure (e.g., redirect to login)

    return null;

  }

};

```


This code defines functions to retrieve tokens from local storage and refresh the access token by sending the refresh token to your backend's `/refresh_token` endpoint.  The `Tokens` interface and return types add type safety.


3. Request Interceptor (TypeScript):


```typescript

api.interceptors.request.use(

  async (config: AxiosRequestConfig): Promise<AxiosRequestConfig> => {

    const { accessToken } = getTokens();


    if (accessToken) {

      config.headers = {

        ...config.headers,

        Authorization: `Bearer ${accessToken}`,

      };

    }


    return config;

  },

  (error) => {

    return Promise.reject(error);

  }

);

```


This interceptor adds the access token to the `Authorization` header of every outgoing request. The TypeScript types ensure type correctness.


4. Response Interceptor (Handling 403 Errors - TypeScript):


```typescript

api.interceptors.response.use(

  (response: AxiosResponse) => {

    return response;

  },

  async (error: any) => {  // Type 'any' here as error structure might vary

    const originalRequest = error.config;


    if (error.response?.status === 403 && !originalRequest._retry) {

      originalRequest._retry = true;


      const newAccessToken = await refreshAccessToken();


      if (newAccessToken) {

        originalRequest.headers = {

          ...originalRequest.headers,

          Authorization: `Bearer ${newAccessToken}`,

        };

        return api(originalRequest); // Retry the original request

      } else {

        return Promise.reject(error); // Refresh failed

      }

    }


    return Promise.reject(error);

  }

);

```


This interceptor handles 403 errors and adds type safety.  Note the use of optional chaining (`error.response?.status`) to handle cases where the error might not have a response object.  The `error: any` is used because the exact structure of the error object can vary depending on the error type. You can create a more specific type if needed.


5. Using the `api` Instance in Your Components (TypeScript):


```typescript

import api from './api';


interface MyData {

  // Define the type of data you expect from the API

  // ...

}


const MyComponent: React.FC = () => {

  const fetchData = async () => {

    try {

      const response = await api.get<MyData>('/my_protected_endpoint'); // Type the response

      const data = response.data; // Access data with type safety

      console.log(data);

    } catch (error) {

      console.error("Error fetching data:", error);

      // Handle errors (e.g., redirect to login)

    }

  };


  // ...

};

```


Import the `api` instance and use it to make your requests. The interceptors will automatically handle token management and refresh logic.  The `<MyData>` generic types the API response, giving you type safety when accessing the data.


Key Takeaway:


This TypeScript implementation provides a clean and centralized way to manage JWTs in your React application. By leveraging Axios interceptors within a custom Axios instance, you can abstract away the complexities of token refresh and significantly reduce code duplication.  Remember that you are using Axios's built-in interceptors functionality – not creating a separate interceptor class – when configuring the `api` instance.  This is a best-practice approach for working with JWTs and Axios in a TypeScript project.  The added TypeScript types significantly enhance code maintainability and prevent common errors.

 
 
 

Recent Posts

See All
What we can learn from cats

That's a fascinating observation, and you've touched upon something quite profound about the apparent inner peace that some animals seem...

 
 
 

Comments


Post: Blog2_Post

Subscribe Form

Thanks for submitting!

©2020 by LearnTeachMaster DevOps. Proudly created with Wix.com

bottom of page