How to Securely Send JWT with Axios Headers

This article provides a straightforward guide on securely attaching JSON Web Tokens (JWT) to HTTP requests using the Axios library. You will learn the standard methods for setting the Authorization header in one-off requests, configuring global defaults, implementing dynamic request interceptors, and applying essential client-side security practices to protect your tokens against common web vulnerabilities.


1. The Standard Authorization Header Format

JWTs are typically transmitted using the Bearer authentication scheme via the standard HTTP Authorization request header.

The format is:

Authorization: Bearer <your_jwt_token>

2. Attaching JWT to Individual Requests

For single requests, pass the headers configuration object as part of the Axios request options:

import axios from 'axios';

const fetchUserData = async (token) => {
  try {
    const response = await axios.get('https://api.example.com/user/profile', {
      headers: {
        Authorization: `Bearer ${token}`
      }
    });
    return response.data;
  } catch (error) {
    console.error('Error fetching data:', error);
    throw error;
  }
};

3. Creating a Reusable Axios Instance

To avoid repeating the header configuration across multiple API calls, create a dedicated Axios instance:

import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json'
  }
});

// Set the token on the instance after login
export const setAuthToken = (token) => {
  if (token) {
    apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  } else {
    delete apiClient.defaults.headers.common['Authorization'];
  }
};

export default apiClient;

4. Dynamic Token Injection with Request Interceptors

The most robust and maintainable approach is using Axios Request Interceptors. This dynamically retrieves the latest token right before the request is dispatched.

import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com'
});

// Request interceptor to attach JWT
api.interceptors.request.use(
  (config) => {
    // Retrieve token from your secure in-memory store or state management
    const token = getAccessToken(); 

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

export default api;

5. Handling Expired Tokens with Response Interceptors

JWT access tokens are typically short-lived. Use an Axios response interceptor to intercept 401 Unauthorized responses and refresh the access token automatically:

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // Check if error is 401 and request has not already been retried
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        // Request a new access token using a refresh token
        const newAccessToken = await refreshAccessToken();
        
        // Update header and retry the original request
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return api(originalRequest);
      } catch (refreshError) {
        // Handle logout or session expiration
        logoutUser();
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

6. Critical Security Best Practices