Why Browser Storage Keeps Stale API Tokens
Debug stale browser tokens by checking localStorage, sessionStorage, cookies, service workers, multiple tabs, refresh timing and logout cleanup.
Quick Answer
Browser storage keeps stale API tokens when logout, refresh, tab sync or environment switching does not clear every place the token can live. Inspect localStorage, sessionStorage, cookies, memory state, service workers and Authorization headers actually sent on the failing request.
Example Scenario
A user logs out and back in, but API calls still use the old account. Another user switches from staging to production and receives 401 errors. DevTools shows an Authorization header with a token that should have been replaced, while the UI displays the new signed-in user.
Step-by-Step Explanation
- Inspect the actual Authorization header on the failing request.
- Check localStorage, sessionStorage, cookies and in-memory auth state.
- Verify refresh token and access token rotation timing.
- Test logout across multiple tabs.
- Check service worker and API client caches.
- Clear environment-specific tokens when switching API base URLs.
The Sent Header Is the Source of Truth
Auth UI can show one state while the request sends another token. The first debugging step is to inspect the failing request in the Network panel and read the Authorization header or cookie behavior. The server only sees what was sent, not what the UI intended.
If the header contains an old token, trace where the request client reads tokens. It may use memory state, localStorage, sessionStorage, cookies or a cached closure created before refresh.
Do not assume clearing one storage location fixes the issue. Modern apps often have several auth layers.
Multiple Tabs Create Race Conditions
One tab can refresh a token while another tab still holds an old token in memory. Logout in one tab may clear storage, but another tab may continue using a cached API client until it reloads or receives a storage event.
Test auth flows with two tabs open. Log in, refresh, logout and switch accounts. Watch whether both tabs update storage and request headers consistently.
Use BroadcastChannel, storage events or a central auth mechanism when multi-tab consistency matters. At minimum, fail safely when a request returns 401.
Cookies and Storage Have Different Rules
Cookies follow domain, path, SameSite, Secure and expiration rules. localStorage is scoped by origin. sessionStorage is scoped by tab. A logout routine that clears localStorage may leave an auth cookie intact, or a cookie clear may target the wrong path.
Inspect cookie domain and path when a token appears to survive logout. A cookie set for .example.com may still be sent to subdomains. A path-specific cookie may not be cleared by a different route.
For sensitive auth, prefer server-supported logout or token revocation in addition to client cleanup.
Service Workers and API Clients Can Cache State
A service worker can intercept requests and attach headers or serve cached responses. An API client module can capture a token value when it is initialized and keep using it after storage changes.
Search for token reads that happen once at startup. A safer request helper reads the current token at request time or subscribes to auth state changes. Avoid hiding token state inside long-lived closures without refresh logic.
If a service worker is involved, test unregistering it in development to see whether the stale behavior disappears. That points to the right layer without changing server code.
Environment Switching Needs Cleanup
Developers and testers often switch between local, staging and production. If the app stores tokens under generic keys, a token from one environment can be sent to another API base URL. The result may be 401, wrong account context or confusing CORS behavior.
Include environment or issuer in storage keys when appropriate. Clear auth state when API base URL or issuer changes. A token issued for staging should not silently ride along to production.
JWT audience and issuer checks on the server should reject cross-environment tokens even if the client accidentally sends them.
Production Checks
Add safe auth diagnostics that show token source, token age, issuer, audience and expiration without printing the token. Pair that with request id and response status.
Test login, logout, refresh, account switch, multi-tab logout and environment switch. Include expired token and revoked token cases.
When fixing stale token bugs, clear storage and memory state together. Then verify the next request header changed, because the header is the only evidence the API will see.
Add a logout integration test that inspects the next API request after logout. It should not contain the old Authorization header, and it should not silently refresh using a stale refresh token.
For account switching, test two accounts with visibly different permissions. If the UI still shows actions from the previous account, the bug may be stale authorization state rather than stale authentication alone.
Token storage choices also affect incident response. A token in memory disappears on reload, while a token in localStorage persists until cleared. Cookies add server-controlled expiration but require careful domain, path and SameSite settings.
If an API client attaches headers through interceptors, inspect interceptor order. A later interceptor can overwrite a fresh Authorization header with an old one, or a retry interceptor can replay a request after logout.
For security-sensitive apps, keep refresh token handling separate from ordinary UI state. A stale access token bug is annoying; a stale refresh token that silently recreates sessions after logout is much more serious.
Check storage events in private browsing and embedded webviews. Some environments restrict storage or behave differently from a normal desktop browser, so a multi-tab sync approach that works locally may fail in a mobile app shell.
When tokens are rotated, include the rotation version or issued-at time in safe diagnostics. That helps distinguish a client that never received the new token from a client that received it but kept sending the old one.
If storage cleanup is part of a migration, include old key names in the cleanup path. Apps often rename token keys over time, leaving older keys behind for users who have not cleared browser storage in months.
Browser extensions and development tools can also inject headers during testing. When a stale token appears only on one machine, compare a clean browser profile before blaming the application storage layer.
For support handoff, record which storage location supplied the token: cookie, localStorage, sessionStorage or memory. That short note points the next debugger to the right cleanup path quickly and safely.
Code Examples
async function apiFetch(url, options = {}) {
const token = localStorage.getItem('access_token');
return fetch(url, {
...options,
headers: { ...options.headers, Authorization: 'Bearer ' + token }
});
} localStorage.removeItem('access_token');
sessionStorage.removeItem('access_token');
// Also clear cookies through the correct domain/path or server logout. const key = 'access_token:' + new URL(API_BASE_URL).host;
localStorage.setItem(key, token); Common Mistakes
- Trusting UI auth state without checking the sent request header.
- Clearing localStorage but leaving auth cookies intact.
- Ignoring multi-tab token refresh and logout behavior.
- Capturing tokens once in a long-lived API client.
- Reusing storage keys across local, staging and production.
FAQ
Where should I look first for stale token bugs?
Inspect the actual request header or cookie sent on the failing API call.
Can logout fail in only one tab?
Yes. Other tabs may keep memory state unless they listen for storage or broadcast events.
Can service workers affect auth requests?
Yes. They can intercept requests or serve cached responses depending on implementation.
Should tokens be shared across environments?
No. Tokens should be scoped to the issuer, audience and environment they were created for.