🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsAnytype Announces Next-Generation Anytwo Platform(11.09.2026 um 21:27 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsAnytype Announces Next-Generation Anytwo Platform(11.09.2026 um 21:27 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 7 Min Lesezeit
0

The Multiple Browser Tab Token Trap: Synchronizing JWT Refresh Across Browser Tabs

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

How multiple open browser tabs can accidentally DDOS your auth server, and how to fix it with the Web Locks API.



Picture this: You’ve just shipped a state-of-the-art Axios response interceptor. You implemented a mutex lock (isRefreshing) and a promise queue (failedQueue) to handle concurrent 401 errors. You tested it within a single tab, and it worked like a charm. You gave yourself a high-five and closed your laptop.



Then, a power user logs in.



Like most humans on the internet, they don't use just one browser tab. They open Tab 1 for User Management, Tab 2 for Analytics, Tab 3 for Settings, and Tab 4 for Support Tickets.



Fifteen minutes pass. Their short-lived JWT access token expires.



The user switches back to Tab 1. In the background, all 4 open tabs wake up, detect the expired token, and fire off four independent POST /auth/refresh-token/ requests at the exact same millisecond.



Tab 1 refreshes the token first, but Tab 2's request arrives a millisecond later, invalidates Tab 1's new token, and Tab 3 nukes the session entirely.



Suddenly, all 4 tabs dump the user back to the login screen.



Welcome to the Cross-Tab Token Trap.









1. The Problem: The Multi-Tab Stampede



In modern single-page applications (SPAs), each browser tab operates in its own isolated JavaScript runtime environment. Memory is not shared.



When an access token expires:





  • isRefreshing = true in Tab A only stops requests inside Tab A.


  • Tab B has no idea Tab A is currently refreshing a token.


  • Tab C lives in complete ignorance of Tabs A and B.




CODE
Tab A (Memory Space 1) ---> isRefreshing = true ---> POST /auth/refresh-token/ (Token Set 1)
Tab B (Memory Space 2) ---> isRefreshing = true ---> POST /auth/refresh-token/ (Token Set 2 -> Revokes Set 1!)
Tab C (Memory Space 3) ---> isRefreshing = true ---> POST /auth/refresh-token/ (Token Set 3 -> Revokes Set 2!)






If your backend enforces Single-Use Refresh Token Rotation (where using a refresh token revokes all previous ones), multi-tab usage causes immediate session destruction.




😂 Reality Check:

You spent two days building an in-memory queue for single-tab concurrency, only to realize your users treat browser tabs like browser bookmarks.










2. The Investigation: "Works in My Tab" Syndrome



Here is how the discovery unfolded during QA:






⏱️ Timeline of a Cross-Tab Mystery





  • 02:00 PM — "Token refresh logic is bulletproof! Unit tests pass!"


  • 02:15 PM — QA reports: "I got logged out while using the dashboard."


  • 02:20 PM — Developer response: "Works on my machine!"


  • 02:45 PM — QA reveals: "Oh, I had 5 tabs open at once."


  • 03:00 PM — Developer opens Chrome DevTools across 3 separate windows.


  • 03:05 PM — Discovered that 3 separate POST /auth/refresh-token/ requests were firing simultaneously from 3 different tab IDs!



The realization hit hard: In-memory boolean flags cannot cross tab boundaries.









3. The Root Cause: Memory Isolation



JavaScript variables like let isRefreshing = false live inside a single tab's context. When Tab A sets isRefreshing = true, Tab B’s memory remains untouched.



To fix this, we need Cross-Tab Concurrency Control.



We needed a way for Tab B to say:




"Hey browser, is any other tab currently refreshing our auth token? If so, I’ll wait. And once they finish, I’ll just grab the new token they saved in localStorage instead of calling the server again!"




Enter the Web Locks API (navigator.locks).









4. The Technical Solution: The Web Locks API



The Web Locks API is a native browser feature supported in all modern browsers (Chrome 69+, Firefox 96+, Safari 15.4+, Edge).



It allows web pages running in the same origin to request an exclusive lock across tabs.






How Web Locks Solve Cross-Tab Refresh:





  1. Exclusive Lock Request: When a 401 error occurs, a tab requests an exclusive lock named 'auth_token_refresh_lock'.


  2. Automatic Synchronization: If Tab A gets the lock, Tab B and Tab C are paused by the browser until Tab A releases the lock.


  3. Double-Check Pattern: When Tab B finally gets the lock, it checks a tokenRefreshedAt timestamp in localStorage. If Tab A refreshed the token less than 5 seconds ago, Tab B skips the API call entirely and simply reuses Tab A's new token!




CODE
Tab A (401) ----> ACQUIRES LOCK 'auth_token_refresh_lock' ----> Fires POST /auth/refresh-token/ ----> Saves token & timestamp to localStorage ----> RELEASES LOCK
|
Tab B (401) ----> WAITS FOR LOCK ---------------------------------------------------------------------------------------------------------------+----> ACQUIRES LOCK ----> Checks timestamp ----> Token updated recently! ----> Skips API Call & Retries Request












5. The Implementation: Production-Ready Code



Here is how to update your axiosInstance.js to support cross-tab synchronization.






Step 1: Web Lock Helper Function



First, create a helper function that wraps navigator.locks.request with a fallback for legacy browser support:




CODE
// src/httpRequest/axiosInstance.js

// Cross-tab lock wrapper using Web Locks API
const runWithCrossTabLock = async (lockName, callback) => {
if ("locks" in navigator) {
// Request exclusive lock across all open browser tabs under the same domain
return navigator.locks.request(lockName, async () => {
return await callback();
});
} else {
// Fallback for older browsers lacking Web Locks support
return await callback();
}
};

// Helper: Check if another tab refreshed the token within the last 5 seconds
const checkIfTokenRefreshedRecently = () => {
const lastRefreshedAt = localStorage.getItem("tokenRefreshedAt");
if (!lastRefreshedAt) return false;

const timeDifference = Date.now() - parseInt(lastRefreshedAt, 10);
return timeDifference < 5000; // Returns true if refreshed < 5s ago
};









Step 2: Response Interceptor with Cross-Tab Lock



Now, update your response interceptor to utilize cross-tab synchronization:




CODE
axiosInstance.interceptors.response.use(
(response) => response.data,
async (error) => {
const originalRequest = error.config;

// Process 401 Unauthorized errors
if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
const refreshToken = localStorage.getItem("refreshToken");
const isRefreshRequest = originalRequest.url?.includes(apiRoutes.refreshToken);

// If no refresh token exists or if the refresh endpoint itself failed, logout
if (!refreshToken || isRefreshRequest) {
handleLogout().catch(console.error);
return Promise.reject(error);
}

originalRequest._retry = true;

// 🔒 CROSS-TAB MUTEX LOCK:
// Only ONE tab across the entire browser can execute this block at a time.
return runWithCrossTabLock("auth_token_refresh_lock", async () => {
const currentAccessToken = localStorage.getItem("accessToken");

// 1. DOUBLE-CHECK PATTERN:
// Did another tab refresh the token while we were waiting for the lock?
if (checkIfTokenRefreshedRecently() && currentAccessToken) {
// YES! Skip network request, update header, and retry
originalRequest.headers.Authorization = `Bearer ${currentAccessToken}`;
return axiosInstance(originalRequest);
}

// 2. WE ARE THE FIRST TAB TO GET THE LOCK:
// Perform the actual network refresh call
try {
const route = `${BASE_URL}${apiRoutes.refreshToken}`;
const response = await axios.post(route, { refresh: refreshToken });
const { access, refresh } = response.data;

// Save new tokens and record timestamp for other tabs
if (access) {
localStorage.setItem("accessToken", access);
localStorage.setItem("tokenRefreshedAt", Date.now().toString());
}
if (refresh) {
localStorage.setItem("refreshToken", refresh);
}

// Retry original request with new token
originalRequest.headers.Authorization = `Bearer ${access}`;
return axiosInstance(originalRequest);
} catch (refreshError) {
handleLogout().catch(console.error);
return Promise.reject(refreshError);
}
});
}

return Promise.reject(error);
}
);












6. Before vs. After

































Scenario In-Memory Interceptor (Before) Web Locks Interceptor (After)
5 Tabs Open simultaneously 5 parallel POST /auth/refresh-token/ network requests
1 single POST /auth/refresh-token/ network request
Token Rotation Behavior Revokes tokens across tabs, forcing full logout Tab 1 refreshes token; Tabs 2–5 reuse the new token seamlessly
Network Overhead High API redundancy on multi-tab power users Zero redundant network calls across tabs
Tab Crash Safety N/A If Tab 1 closes mid-refresh, browser automatically unlocks Tab 2








7. Things I Learned




💡 Lesson #1: Web Locks are Tab-Crash Safe

Unlike custom localStorage mutex flags (which stay stuck if a tab closes mid-execution), navigator.locks automatically releases the lock if a tab closes or crashes unexpectedly.



⚠️ Gotcha #2: Always Double-Check After Acquiring a Lock

Acquiring a lock is only half the battle. You MUST inspect shared state (localStorage timestamp) immediately after acquiring the lock to see if the work was already completed by a previous lock holder!



🚀 Improvement #3: Combine with BroadcastChannel for Instantly Synced Logouts

Use new BroadcastChannel('auth_channel') to post a message when a user explicitly logs out in Tab A, so Tab B instantly redirects without waiting for a 401 error!










8. Conclusion



Handling authentication in single-page applications is rarely just about single HTTP calls. Once users open multiple tabs, your frontend becomes a distributed system running on the user's machine.



By combining Axios Interceptors with the Web Locks API, we eliminated cross-tab token refresh race conditions completely—without adding external libraries or complex backend hacks.



Now, your users can open 50 tabs at once, and your application will handle token updates silently like a charm! 🚀

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
The Gemini desktop app is now available for Windows
1 Quelle
Anytype Announces Next-Generation Anytwo Platform
1 Quelle
Windows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC