From eaa518b443ce919d853dcf960df7a84238d18651 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Mon, 23 Mar 2026 19:14:31 +0000 Subject: [PATCH] Fix emergency token being rejected by axios interceptor Non-JWT tokens (like emergency access tokens) were treated as expired by isTokenExpired(), triggering a MSAL silent refresh that fails and clears the token. Fix: non-JWT tokens are treated as never-expired and skip the 401-retry refresh path. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/api/client.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 2a8b0e4..3cd36d7 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -14,11 +14,14 @@ export function registerTokenRefresher(fn: TokenRefresher) { function isTokenExpired(token: string): boolean { try { - const payload = JSON.parse(atob(token.split('.')[1])) + const parts = token.split('.') + if (parts.length !== 3) return false // not a JWT (e.g. emergency token) — never expires + const payload = JSON.parse(atob(parts[1])) + if (!payload.exp) return false // Refresh 60s before actual expiry return payload.exp * 1000 < Date.now() + 60_000 } catch { - return true + return false } } @@ -43,12 +46,14 @@ api.interceptors.request.use(async (config) => { return config }) -// On 401 — try once more with a fresh token, then reload +// On 401 — try once more with a fresh token (JWT only), then reload api.interceptors.response.use( res => res, async (error) => { const originalRequest = error.config - if (error.response?.status === 401 && !originalRequest._retry && _refreshToken) { + const currentToken = sessionStorage.getItem('ac_access_token') ?? '' + const isJwt = currentToken.split('.').length === 3 + if (error.response?.status === 401 && !originalRequest._retry && _refreshToken && isJwt) { originalRequest._retry = true try { const fresh = await _refreshToken()