DEV Community

Cover image for Handling JWT Refresh Tokens in Axios without the Headache
Tai Tran
Tai Tran

Posted on

Handling JWT Refresh Tokens in Axios without the Headache

If you build Frontend applications with React, Vue, or Angular, you’ve probably faced this scenario:

Your user's Access Token expires.

The user loads a dashboard that fires 3 API requests simultaneously.

All 3 requests fail with 401 Unauthorized.

Your app tries to refresh the token... 3 times in a row. 💥

The first refresh succeeds, but the second one invalidates the first one. The user gets logged out randomly.

This is called the Race Condition.

To fix this, you need a complex logic: A Promise Queue. You need to pause all failed requests, wait for one refresh to happen, and then retry them all with the new token.

I got tired of copy-pasting this boilerplate code into every project, so I built a tiny, battle-tested library to handle it for me.

Meet axios-auth-refresh-queue.

Why use this instead of coding it yourself?
⚡ Ultra-lightweight: It’s 641 Bytes (minified + gzipped). Yes, less than 1KB.

🛡 Bulletproof: Handles race conditions, infinite loops, and failed refreshes gracefully.

🐞 Debug Mode: Comes with a built-in logger to see exactly what's happening (Refreshing? Queuing? Retrying?).

🟦 TypeScript: Fully typed out of the box.

How to use it
It takes less than 2 minutes to set up.

  1. Install
    npm install axios-auth-refresh-queue

  2. The Setup
    You just need two things: a function to refresh your token and the interceptor setup.

import axios from 'axios';
import { applyAuthTokenInterceptor } from 'axios-auth-refresh-queue';

// 1. Create your Axios instance
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
});

// 2. Define your Refresh Logic
// This function should return the new access token
const requestRefresh = async (refreshToken: string) => {
  const response = await axios.post('/auth/refresh', { token: refreshToken });
  return {
    accessToken: response.data.accessToken,
    refreshToken: response.data.refreshToken,
  };
};

// 3. Apply the interceptor
applyAuthTokenInterceptor(apiClient, {
  requestRefresh,  // The async function to call backend
  debug: true,     // 🐞 Enable console logs to see the magic!

  onSuccess: (newTokens) => {
    // Save new tokens to localStorage/Store
    localStorage.setItem('token', newTokens.accessToken);
  },

  onFailure: (error) => {
    // Refresh failed? Log the user out
    console.error('Session expired', error);
    window.location.href = '/login';
  }
});

export default apiClient;
Enter fullscreen mode Exit fullscreen mode

That's it! Now, whenever a 401 happens:

The library pauses all other requests.

It calls your requestRefresh function once.

It updates the header and retries all original requests automatically.

Cool Features
🐞 Debug Mode
Not sure if it's working? Just enable debug: true and check your console:

[Auth-Queue] 🚨 401 Detected from /api/user
[Auth-Queue] ⏳ Refresh already in progress. Adding to queue...
[Auth-Queue] ✅ Refresh Successful! Retrying queued requests.
Enter fullscreen mode Exit fullscreen mode

⏩ Skip Auth
Need to call a public API that might return 401 but shouldn't trigger a refresh?

axios.get('/api/public-status', { 
    skipAuthRefresh: true 
});
Enter fullscreen mode Exit fullscreen mode

Give it a try!
I built this to save time for myself and my team, and I hope it helps you too. It’s open-source, fully tested, and ready for production.

📦 NPM: npmjs.com/package/axios-auth-refresh-queue
🐙 GitHub: https://github.com/Eden1711/axios-auth-refresh

If you find it useful, a ⭐️ on GitHub would mean the world to me!

Happy coding! 💻

Top comments (0)