JAVASCRIPT
Client-Side JWT/OAuth Token Refresh
Implement client-side automatic JWT or OAuth access token refreshing to maintain user sessions and securely interact with APIs without re-authentication.
// Assume these functions handle storing/retrieving tokens (e.g., in localStorage)
const getAccessToken = () => localStorage.getItem('accessToken');
const getRefreshToken = () => localStorage.getItem('refreshToken');
const setAccessToken = (token) => localStorage.setItem('accessToken', token);
const setRefreshToken = (token) => localStorage.setItem('refreshToken', token);
const clearTokens = () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
// Redirect to login page or update UI
console.log('Tokens cleared. User might need to log in again.');
};
let isRefreshing = false;
let refreshSubscribers = [];
async function refreshAccessToken(refreshTokenEndpoint) {
if (isRefreshing) {
return new Promise(resolve => {
refreshSubscribers.push(resolve);
});
}
isRefreshing = true;
const currentRefreshToken = getRefreshToken();
if (!currentRefreshToken) {
clearTokens();
throw new Error('No refresh token available. User must re-authenticate.');
}
try {
const response = await fetch(refreshTokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: currentRefreshToken })
});
if (!response.ok) {
clearTokens();
throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
}
const data = await response.json();
setAccessToken(data.accessToken);
if (data.refreshToken) { // Refresh token might also be rotated
setRefreshToken(data.refreshToken);
}
console.log('Access token refreshed successfully.');
// Resolve all queued requests
refreshSubscribers.forEach(callback => callback(data.accessToken));
refreshSubscribers = []; // Clear subscribers
return data.accessToken;
} catch (error) {
console.error('Error refreshing token:', error);
clearTokens(); // Invalidate session if refresh fails
throw error;
} finally {
isRefreshing = false;
}
}
// Example of an authenticated API call wrapper
async function authenticatedFetch(url, options = {}, refreshTokenEndpoint) {
let accessToken = getAccessToken();
if (!accessToken) {
// Handle case where no token is present initially, e.g., redirect to login
throw new Error('No access token found. User not authenticated.');
}
options.headers = {
...options.headers,
Authorization: `Bearer ${accessToken}`
};
let response = await fetch(url, options);
if (response.status === 401) { // Token expired or invalid
console.log('Access token expired. Attempting refresh...');
try {
const newAccessToken = await refreshAccessToken(refreshTokenEndpoint);
// Retry the original request with the new token
options.headers.Authorization = `Bearer ${newAccessToken}`;
response = await fetch(url, options);
} catch (refreshError) {
console.error('Authentication failed after refresh attempt:', refreshError);
clearTokens(); // Ensure user is logged out if refresh failed
throw refreshError; // Propagate the error
}
}
if (!response.ok) {
throw new Error(`API call failed with status ${response.status}: ${response.statusText}`);
}
return response.json();
}
// --- Usage Example ---
// const REFRESH_ENDPOINT = 'https://api.example.com/auth/refresh-token';
// const PROTECTED_API_ENDPOINT = 'https://api.example.com/protected-data';
// // Initial login (would set accessToken and refreshToken)
// // setAccessToken('initial_access_token');
// // setRefreshToken('initial_refresh_token');
// // Call protected API
// // authenticatedFetch(PROTECTED_API_ENDPOINT, {}, REFRESH_ENDPOINT)
// // .then(data => console.log('Protected data:', data))
// // .catch(error => console.error('Error accessing protected data:', error));
How it works: This JavaScript snippet provides a robust solution for client-side access token refreshing, a common requirement for secure API integrations using JWTs or OAuth 2.0. It includes `refreshAccessToken` to make a call to an authentication server to exchange a refresh token for a new access token. The `authenticatedFetch` wrapper handles automatic detection of expired tokens (401 status), triggers the refresh process, and then retries the original API request with the new token. It also incorporates a mechanism to queue multiple requests that hit an expired token concurrently, ensuring only one refresh request is sent.