From df6c0dece5c29d841468eec649fc5660d7bf538c Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Thu, 18 Jun 2026 16:34:35 +0100 Subject: [PATCH] fix(frontend): update token refresh threshold and fix race condition The 14-day refresh threshold was designed for the old 30-day tokens but the backend now emits 1-hour tokens. The threshold caused every page load to trigger an immediate /refresh-token call, which revoked the old JTI mid-flight while concurrent requests (profile, giftcards, bookings, etc.) were still using it, causing 401 errors. Changes: - Refresh threshold: 14 days -> 5 minutes (matches 1-hour token lifetime) - Interval check: 60 min -> 2 min (must be shorter than threshold) - Race condition: refresh now completes before profile fetch, preventing JTI invalidation during concurrent requests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- frontend/src/lib/stores/auth.svelte.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/stores/auth.svelte.ts b/frontend/src/lib/stores/auth.svelte.ts index f62c3b5..5ef46c2 100644 --- a/frontend/src/lib/stores/auth.svelte.ts +++ b/frontend/src/lib/stores/auth.svelte.ts @@ -32,14 +32,16 @@ class AuthStore { constructor() { if (browser) { this.initializeAuth(); - // Check token refresh every hour + // Check token refresh every 2 minutes + // (must be shorter than the 5-minute threshold so the + // refresh check fires before the token actually expires) setInterval( () => { if (this.token) { this.refreshTokenIfNeeded(); } }, - 60 * 60 * 1000 + 2 * 60 * 1000 ); } } @@ -78,9 +80,13 @@ class AuthStore { firstName: '', lastName: '' }; - this.fetchUserProfile(); - // Check if token needs refresh - this.refreshTokenIfNeeded(); + + // Refresh first so any JTI invalidation from rotation + // happens before other requests use this token. + // Then fetch profile with the (potentially refreshed) token. + this.refreshTokenIfNeeded().then(() => { + this.fetchUserProfile(); + }); } else { this.clearAuth(); } @@ -195,9 +201,10 @@ class AuthStore { return; } - // Refresh if token expires in less than 14 days - const fourteenDays = 14 * 24 * 60 * 60 * 1000; - if (decoded.exp * 1000 - Date.now() < fourteenDays) { + // Refresh if token expires in less than 5 minutes + // (1-hour token lifetime from backend) + const fiveMinutes = 5 * 60 * 1000; + if (decoded.exp * 1000 - Date.now() < fiveMinutes) { try { const response = await fetch('/api/refresh-token', { method: 'POST',