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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs