JAVASCRIPT
Managing API Authentication with Access and Refresh Tokens
Learn to securely manage user authentication with access and refresh tokens in JavaScript, implementing token renewal logic to maintain active sessions.
const API_BASE_URL = 'https://api.example.com';
let accessToken = localStorage.getItem('accessToken');
let refreshToken = localStorage.getItem('refreshToken');
async function refreshAuthToken() {
if (!refreshToken) {
throw new Error('No refresh token available. User must re-authenticate.');
}
try {
const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
}
const data = await response.json();
accessToken = data.accessToken;
refreshToken = data.refreshToken || refreshToken; // Refresh token might also be updated
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
console.log('Tokens refreshed successfully.');
return accessToken;
} catch (error) {
console.error('Error refreshing token:', error);
// On refresh failure, clear tokens and prompt re-login
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
accessToken = null;
refreshToken = null;
// Potentially redirect to login page
// window.location.href = '/login';
throw error;
}
}
async function authenticatedFetch(endpoint, options = {}) {
let headers = options.headers || {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
try {
let response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
if (response.status === 401 && refreshToken) { // Token expired or invalid
console.log('Access token expired, attempting to refresh...');
await refreshAuthToken(); // Attempt to get a new access token
// Retry the original request with the new access token
headers['Authorization'] = `Bearer ${accessToken}`;
response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
}
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response;
} catch (error) {
console.error('Authenticated fetch error:', error);
throw error;
}
}
// Example Usage:
// Make sure to set initial tokens, e.g., after user login:
// localStorage.setItem('accessToken', 'your_initial_access_token');
// localStorage.setItem('refreshToken', 'your_initial_refresh_token');
// (async () => {
// try {
// const userData = await authenticatedFetch('/user/profile').then(res => res.json());
// console.log('User Profile:', userData);
// // Simulate a request that will fail due to expired token (if configured on server)
// // Then expect it to be retried after refresh.
// const secureData = await authenticatedFetch('/secure/data').then(res => res.json());
// console.log('Secure Data:', secureData);
// } catch (error) {
// console.error('Failed to fetch authenticated data:', error.message);
// }
// })();
How it works: This snippet demonstrates a robust pattern for handling API authentication using access and refresh tokens. The `authenticatedFetch` function automatically attaches the current `accessToken` to requests. If an API call returns a 401 Unauthorized status, indicating an expired access token, it attempts to use the `refreshToken` to acquire a new `accessToken` from a dedicated refresh endpoint. Upon successful token renewal, the original failed request is transparently retried with the new token, ensuring a seamless user experience without requiring re-login until the refresh token itself expires or becomes invalid.