Axios Interceptors End the Tedium of Attaching JWTs to Every Request
In the previous article, we already solved the generation and verification of Tokens.
Now a practical problem arises:
If our project has 20 endpoints, do we have to manually write this every time?
headers: {
Authorization: `Bearer ${token}`
}
Of course not.
So this article is about understanding Axios request interceptors.
1. First, look at a very cumbersome approach
Suppose there are three APIs:
axios.get('/repo', {
headers: {
Authorization: `Bearer ${token}`
}
})
axios.get('/user', {
headers: {
Authorization: `Bearer ${token}`
}
})
axios.post('/order', data, {
headers: {
Authorization: `Bearer ${token}`
}
})
You will find:
Every request is handling the Token repeatedly.
But the Token is fundamentally shared logic for the entire login system.
So it should be handled uniformly.
2. We need a place for "pre-request unified processing"
We previously created:
src/api/config.js
Now it is not only responsible for:
baseURL
timeout
It is also responsible for:
Automatically placing the Token into the Header before the request is sent.
What Axios provides is:
interceptors.request
That is:
Request Interceptor.
3. Adding a request interceptor
Modify:
src/api/config.js
Code:
import axios from 'axios';
const instance = axios.create({
baseURL: '/api',
timeout: 5000
});
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
export default instance;
The key part here is:
instance.interceptors.request.use(...)
It can be understood as:
"From now on, for any request made using this Axios instance, let me check it first."
4. When exactly does the interceptor execute?
Suppose a page calls:
getRepo()
Then:
axios.get('/repo')
The request does not fly out directly.
Instead, it goes through:
Page calls getRepo()
↓
repo.js calls axios.get()
↓
Axios request interceptor
↓
Reads Token from localStorage
↓
Adds Authorization
↓
Actually sends the request
So the greatest significance of the interceptor is:
Unified handling of all shared logic for requests.
5. Why fetch the Token from localStorage?
Because after a successful login, we must first save the Token.
For example:
localStorage.setItem('token', token);
Later:
localStorage.getItem('token');
can retrieve it.
So the whole relationship is:
Login succeeds
↓
Token saved to localStorage
↓
Any API request is sent
↓
Interceptor reads Token
↓
Adds Authorization
6. Why write it as Bearer?
Ultimately, we want the Header to be:
Authorization: Bearer eyJ...
So the code:
config.headers['Authorization'] = `Bearer ${token}`;
Note the space here:
Bearer + space + Token
This is the standard Bearer Token format.
If written as:
`Bearer${token}`
It would become:
BearereyJ...
When the backend checks using:
authorization.startsWith('Bearer ')
it would fail.
7. This makes the API layer very clean
user.js:
import axios from './config';
export const login = async (data) => {
const res = await axios.post('/login', data);
return res.data;
}
repo.js:
import axios from './config';
export const getRepo = async () => {
const res = await axios.get('/repo');
return res.data;
}
You will find:
These two files do not need to care about the Token at all.
They only need to express:
"Which endpoint do I want to call?"
As for:
"Is there a Token?"
"Which Header does the Token go into?"
These matters are uniformly delegated to Axios.
This is separation of concerns.
8. Why is the request interceptor important?
Because many projects have things that "all requests must do":
Token
Unified Headers
Request logging
Request parameter processing
Loading
Unified error handling
These are not suitable to be scattered throughout business code.
So:
Business code
is responsible for business logic
Interceptors
are responsible for shared logic
This makes the project much clearer.
9. After the server receives the request
Now the request sent by the browser looks like:
GET /api/repo
Authorization: Bearer eyJ...
Mock backend:
const authorization = req.headers?.authorization;
Then:
const token = authorization.slice('Bearer '.length);
Finally:
jwt.verify(token, secret);
The entire request chain is now complete:
Page calls API
↓
Axios request interceptor automatically adds Token
↓
Request sent to /api/repo
↓
Mock reads Authorization
↓
Extracts Token
↓
jwt.verify()
↓
Returns data
The next article will not address "how the server verifies," but rather:
How does React itself know whether the current user is logged in?
That's when Zustand comes into play.