Duplicate token refreshes when multiple Axios requests receive 401
0 reputation · 15 Sept 2025, 23:38 UTC
0 reputation · 15 Sept 2025, 23:38 UTC
Design a least‑privilege authentication flow where each Axios request carries only the scopes it needs and expired credentials are refreshed transparently.
Axios provides per‑request auth config and response interceptors, but it lacks a built‑in queue or deduplication mechanism for token refresh. When several concurrent requests encounter a 401, each interceptor can independently trigger a refresh, causing redundant network calls and possible race conditions.
Use a module‑level refreshPromise that is created once when the first 401 is seen. All subsequent 401 handlers simply return refreshPromise; after the refresh resolves, the original request is retried with the new token. This guarantees a single network call regardless of how many concurrent requests hit 401.
Axios has no built‑in queue for 401‑triggered refreshes. When two or more requests finish simultaneously with 401, each response interceptor runs in isolation and starts its own refresh request. The race can lead to duplicate refreshes and inconsistent token state.
axios.interceptors.request.use and axios.interceptors.response.use but no queue or deduplication primitives.let refreshPromise = null;
axios.interceptors.request.use(config => {
// Assume a function getScopedToken(config) that returns the token for the scopes needed by this request.
const token = getScopedToken(config);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
axios.interceptors.response.use(undefined, async error => {
const { config, response } = error;
if (!response || response.status !== 401) return Promise.reject(error);
// If a refresh is already in flight, wait for it.
if (!refreshPromise) {
refreshPromise = refreshToken();
refreshPromise.finally(() => { refreshPromise = null; });
}
try {
await refreshPromise;
// Retry the original request with the new token.
config.headers.Authorization = `Bearer ${getScopedToken(config)}`;
return axios(config);
} catch (refreshError) {
// If refresh failed, propagate the original 401.
return Promise.reject(error);
}
});
refreshToken() should return a promise that resolves when the new token is stored (e.g., in localStorage or a state store).axios.create() instances, each instance must maintain its own refreshPromise to avoid cross‑service interference.If you are using separate Axios instances or a custom token‑retrieval strategy, let me know so I can tailor the code to that setup.
refreshPromise with a finally block to clear it.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.