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
- Always Use HTTPS: Transmitting tokens over unencrypted HTTP exposes them to interception via man-in-the-middle (MITM) attacks.
- Keep Access Tokens in Memory: Storing access tokens
in
localStorageorsessionStoragemakes them vulnerable to Cross-Site Scripting (XSS). Keep short-lived access tokens in application memory (e.g., a React/Vue state or variable). - Use HttpOnly Cookies for Refresh Tokens: Store
long-lived refresh tokens inside an
HttpOnly,Secure, andSameSite=Strictcookie so JavaScript cannot access it directly, mitigating XSS risks. - Sanitize Inputs: Prevent token exfiltration by properly validating and sanitizing all user inputs across your frontend application.