Intercept and Decode JWT Claims in Axios
Axios request interceptors provide a built-in mechanism to capture
outgoing HTTP requests before they are transmitted over the network. By
attaching a request interceptor to an Axios instance, you can retrieve a
JSON Web Token (JWT) from storage, decode its payload claims to inspect
metadata such as expiration times or user permissions, handle token
refresh logic if necessary, and attach the valid token to the
Authorization header.
How Axios Request Interceptors Work
Axios executes middleware-like functions known as interceptors prior
to dispatching a network request. Using
axios.interceptors.request.use(), you can access and mutate
the request configuration object (AxiosRequestConfig).
When processing JWTs, this hook serves as the optimal gatekeeper to:
- Retrieve the serialized token from memory or persistent storage.
- Decode the Base64Url-encoded payload without executing a full cryptographic verification (which is handled by the server).
- Validate client-side claims, such as checking if the token has
expired (
exp). - Attach the formatted Bearer token to the
Authorizationheader.
Decoding JWT Claims
A JWT consists of three parts separated by dots: Header, Payload, and
Signature. The payload contains the claims. Decoding can be achieved
using a standard Base64 decoding function or lightweight libraries like
jwt-decode.
Pure JavaScript Decoding Function
function decodeJwtPayload(token) {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
return JSON.parse(jsonPayload);
} catch (error) {
return null;
}
}Implementing the Interceptor
Below is the complete implementation of an Axios instance configured to decode and validate JWT claims before request dispatch:
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
apiClient.interceptors.request.use(
async (config) => {
const token = localStorage.getItem('access_token');
if (!token) {
return config;
}
const claims = decodeJwtPayload(token);
if (claims) {
const currentTime = Math.floor(Date.now() / 1000);
// Check if token has expired based on the 'exp' claim
if (claims.exp && claims.exp < currentTime) {
// Handle token expiration: Refresh token logic or redirect
const newToken = await refreshAuthToken();
config.headers.Authorization = `Bearer ${newToken}`;
return config;
}
// Check custom claims or roles if necessary
if (claims.requiresMfa) {
config.headers['X-MFA-Required'] = 'true';
}
}
// Attach token to Authorization header
config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
async function refreshAuthToken() {
// Logic to call refresh endpoint and return the new access token
const refreshToken = localStorage.getItem('refresh_token');
const response = await axios.post('https://api.example.com/auth/refresh', {
refreshToken,
});
const newToken = response.data.accessToken;
localStorage.setItem('access_token', newToken);
return newToken;
}
export default apiClient;Key Considerations
- Client-Side Decoding vs. Verification: Decoding a token on the client extracts claim data for UI state and flow control, but it does not guarantee token integrity. Cryptographic verification remains strictly the backend server's responsibility.
- Asynchronous Handling: Axios request interceptors
natively support
async/await. If a token is expired, the interceptor can pause the outgoing request, await a new token from a refresh endpoint, update headers, and resolve the request without failing the initial operation. - Error Handling: If token parsing fails or the
refresh operation encounters a rejection, reject the interceptor with
Promise.reject(error)to cancel the request and allow your application's error boundaries to respond accordingly.