Handling Access Token Expiration Gracefully
Strategies for handling expired access tokens including proactive refresh, retry with refresh, and silent re-authentication patterns.
Detailed Explanation
Handling Token Expiration
Access tokens are intentionally short-lived (typically 5-60 minutes). When they expire, API requests return 401 Unauthorized. A well-designed client handles this transparently without disrupting the user experience.
Strategy 1: Proactive Refresh
Check the token's expiration before each API request. If it expires within a buffer window (e.g., 30 seconds), refresh proactively:
async function apiRequest(url, options = {}) {
// Check if token expires soon (30-second buffer)
if (tokenExpiresAt - Date.now() < 30000) {
await refreshAccessToken();
}
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${accessToken}`,
},
});
}
Strategy 2: Reactive Refresh (401 Retry)
Attempt the request. If it returns 401, refresh the token and retry once:
async function apiRequest(url, options = {}) {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${accessToken}`,
},
});
if (response.status === 401) {
await refreshAccessToken();
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${accessToken}`,
},
});
}
return response;
}
Strategy 3: Token Refresh Queue
When multiple API requests fail simultaneously, avoid making multiple refresh requests. Use a queue:
let refreshPromise = null;
async function ensureValidToken() {
if (refreshPromise) return refreshPromise;
refreshPromise = refreshAccessToken().finally(() => {
refreshPromise = null;
});
return refreshPromise;
}
When Refresh Fails
If the refresh token is also expired or revoked:
- Clear all stored tokens
- Redirect the user to the login page
- Preserve the user's current URL for post-login redirect
- Show a user-friendly message ("Your session has expired. Please sign in again.")
Best Practices
- Always check
expires_infrom the token response to calculate expiration time - Use a buffer (30-60 seconds) to avoid edge cases
- Implement a single-flight refresh mechanism to prevent concurrent refresh requests
- Handle network errors during refresh gracefully
Use Case
A React application that makes frequent API calls. The app uses an Axios interceptor to check for 401 responses, automatically refreshes the access token using the stored refresh token, and retries the original request — all transparent to the user.