Centralizing Authentication with Axios Interceptors: One Token, Zero Boilerplate
Use Axios interceptors to inject JWTs into every request and handle 401/403 errors globally. Centralize auth logic, simplify tests, and keep services clean. Follow this practical example and trade‑off guide to get started.
30 Sept 2026, 14:22 UTC

Problem: Repetitive Header Logic in Every Service
When a front‑end talks to a protected API, every request usually needs an Authorization header containing a JWT or an OAuth bearer token. A naïve implementation repeats that header logic in every service module:
import axios from 'axios';
export const getUser = () => axios.get('/api/user', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
Duplicated code is hard to maintain, introduces subtle bugs when the token changes, and makes unit tests noisy because each call must mock the header.
Interceptor Fundamentals
Axios provides a middleware‑style API: interceptors.request runs before a request is sent, and interceptors.response runs after a response returns (or fails). By attaching logic here, you centralize concerns such as authentication, global error handling, or request logging.
Key points to remember:
- Interceptors are executed in the order they are added.
- They receive the
configobject (for requests) orresponse/errorobjects (for responses). - Returning a promise from the interceptor allows asynchronous work, such as refreshing a token.
Practical Example: A Token‑Injected Axios Instance
Below is a minimal, production‑ready setup that:
- Creates a dedicated Axios instance.
- Attaches a request interceptor to add the
Authorizationheader. - Adds a response interceptor to catch 401/403 responses, trigger a logout, and optionally retry after refreshing the token.
import axios from 'axios';
// 1. Create a dedicated instance
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
// 2. Request interceptor: inject token
api.interceptors.request.use(
config => {
const token = localStorage.getItem('jwt');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 3. Response interceptor: handle auth errors
api.interceptors.response.use(
response => response, // pass through successful responses
async error => {
const originalRequest = error.config;
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
// Optional: attempt token refresh
try {
const refreshToken = localStorage.getItem('refresh');
const result = await axios.post('/auth/refresh', { refreshToken });
const newToken = result.data.jwt;
localStorage.setItem('jwt', newToken);
// Update the failed request with the new token
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
return api(originalRequest); // retry
} catch (refreshError) {
// Refresh failed – force logout
localStorage.removeItem('jwt');
localStorage.removeItem('refresh');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;
Usage in a service module becomes trivial:
import api from './api';
export const fetchProfile = () => api.get('/user/profile');
Verification Checklist
- Run
api.get('/test-header')against a mock server that echoes headers. ConfirmAuthorizationis present. - Simulate a 401 response. Verify the interceptor redirects to
/loginand clears storage. - For the refresh flow, mock a successful
/auth/refreshand ensure the original request is retried with the new token. - Add
console.loginside each interceptor to confirm execution order.
Trade‑offs & Limitations
- Performance: Interceptors run for every request. Heavy computation or logging can add latency, especially in high‑traffic apps. Keep logic lean.
- Order Sensitivity: Adding a request interceptor after a response interceptor can lead to confusing behaviour. Always attach request interceptors first.
- Cancellation Semantics: When using
AbortControlleror custom adapters, interceptors may need explicit handling to preserve cancellation. Otherwise, a cancelled request could still trigger interceptors. - Testing Complexity: While interceptors centralize logic, unit tests must mock the Axios instance rather than each service. This is usually simpler but requires awareness of the instance’s global state.
Actionable Next Steps
- Replace all direct
axioscalls in your codebase with the dedicatedapiinstance. - Move any hard‑coded
Authorizationheader logic into the request interceptor. - Implement a token refresh endpoint if you don’t already have one, and wire it into the response interceptor.
- Set up automated tests that mock the Axios instance and assert header injection and error handling.
- Monitor request latency after adding interceptors; if you notice a spike, profile the interceptor code for bottlenecks.
By centralizing authentication in Axios interceptors, you eliminate boilerplate, reduce bugs, and simplify testing. Once the instance is in place, every service module can focus on business logic rather than header plumbing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.