Wei-Shaw/sub2api · error
AUTH_SESSION_CHANGED
AUTH_SESSION_CHANGED
Error message
Authentication session changed while refreshing.
What it means
Thrown by the axios response interceptor in frontend/src/api/client.ts when a token refresh attempt fails AND the locally stored session (refresh_token or auth_user in localStorage) no longer matches the values captured before the refresh started. It signals that another tab or flow replaced or logged out the session while this request's refresh was in flight. The rejection carries status 401 and code AUTH_SESSION_CHANGED so callers can distinguish it from a plain expired session. Importantly, this path deliberately does NOT clear the stored tokens, because the newer session in localStorage is authoritative.
Source
Thrown at frontend/src/api/client.ts:198
? authHeader.slice('Bearer '.length)
: null
const tokens = await refreshAuthTokens({ failedAccessToken })
// Retry the original request with the refreshed token
if (originalRequest.headers) {
originalRequest.headers.Authorization = `Bearer ${tokens.access_token}`
}
return apiClient(originalRequest)
} catch {
// A stale request must never destroy a session that was logged out or replaced while
// its refresh was in flight (for example, when another tab signs in as another user).
const sessionChanged =
localStorage.getItem('refresh_token') !== refreshToken ||
localStorage.getItem('auth_user') !== refreshSessionUser
if (sessionChanged) {
return Promise.reject({
status: 401,
code: 'AUTH_SESSION_CHANGED',
message: 'Authentication session changed while refreshing.'
})
}
// Clear tokens and redirect to login
localStorage.removeItem('auth_token')
localStorage.removeItem('refresh_token')
localStorage.removeItem('auth_user')
localStorage.removeItem('token_expires_at')
sessionStorage.setItem('auth_expired', '1')
if (!window.location.pathname.includes('/login')) {
window.location.href = '/login'
}
return Promise.reject({
status: 401,
code: 'TOKEN_REFRESH_FAILED',View on GitHub (pinned to 073e92d171)
Solutions
- If a login/redirect flow is racing in-flight API calls, cancel or await pending requests before replacing localStorage auth keys.
- In the app's axios error handler, treat code==='AUTH_SESSION_CHANGED' as non-fatal: do not force redirect, just invalidate the affected request (refetch with the new token or silently drop it).
- Ensure only one tab performs refresh (e.g. use a BroadcastChannel/web-lock around the refresh call) so stale refreshes stop competing with new sessions.
- Verify nothing in your code writes localStorage 'auth_user' or 'refresh_token' from stale state after a successful login elsewhere.
Example fix
// before
apiClient.get('/me').catch((e) => {
if (e.status === 401) window.location.href = '/login' // also fires for AUTH_SESSION_CHANGED
})
// after
apiClient.get('/me').catch((e) => {
if (e.code === 'AUTH_SESSION_CHANGED') return retryWithFreshToken('/me') // session is valid, just this request is stale
if (e.status === 401) window.location.href = '/login'
}) Defensive patterns
Strategy: try-catch
Type guard
function isAuthSessionChanged(e: unknown): e is { status: number; code: 'AUTH_SESSION_CHANGED'; message: string } {
return typeof e === 'object' && e !== null && (e as any).code === 'AUTH_SESSION_CHANGED'
} Try / catch
try {
await apiClient.get('/me')
} catch (e) {
if (isAuthSessionChanged(e)) {
// Session in localStorage is newer than this request; retry with fresh state
// instead of redirecting — the tokens were intentionally NOT cleared.
return retryAfterReauth()
}
throw e
} Prevention
- Serialize refresh attempts across tabs (BroadcastChannel or Web Locks) so only one refresh runs at a time.
- Before overwriting localStorage auth keys on login/logout, abort in-flight requests holding old tokens.
- Never auto-redirect to /login on code AUTH_SESSION_CHANGED — that path is reserved for TOKEN_REFRESH_FAILED.
- Read the current token from storage per request, not from a module-level cache.
When it happens
Trigger: A request gets a 401, enters the refresh flow, and the refresh call fails; meanwhile localStorage['refresh_token'] or localStorage['auth_user'] changed (e.g. another tab signed in as a different user, or a logout happened between capturing refreshToken/refreshSessionUser and the failed refresh). Any stale in-flight request from the old session then rejects with this error.
Common situations: Multi-tab apps where one tab re-authenticates while another has stale in-flight requests; logout in one tab during a refresh in another; embedded iframes or PWA service-worker requests that outlive a session swap; race between a sign-in redirect and queued 401 retries.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- TOKEN_REFRESH_FAILED
- Passkeys are not supported by this browser
- Passkey sign-in was cancelled
- Passkey creation was cancelled
- HTTP error! status: ${response.status}
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/178460c16fbae12a.
Report an issue: GitHub.