diff --git a/src/platform/auth/session/useSessionCookie.ts b/src/platform/auth/session/useSessionCookie.ts index 49f6fec46..8a777155e 100644 --- a/src/platform/auth/session/useSessionCookie.ts +++ b/src/platform/auth/session/useSessionCookie.ts @@ -10,32 +10,49 @@ export const useSessionCookie = () => { /** * Creates or refreshes the session cookie. * Called after login and on token refresh. + * Implements retry logic with token refresh for handling timing issues. */ const createSession = async (): Promise => { if (!isCloud) return const authStore = useFirebaseAuthStore() - const authHeader = await authStore.getAuthHeader() - if (!authHeader) { - throw new Error('No auth header available for session creation') - } + // Simple retry with forceRefresh for token timing issues + for (let attempt = 0; attempt < 3; attempt++) { + // First attempt uses cached token, retries force refresh + const authHeader = await authStore.getAuthHeader(attempt > 0) - const response = await fetch(api.apiURL('/auth/session'), { - method: 'POST', - credentials: 'include', - headers: { - ...authHeader, - 'Content-Type': 'application/json' + if (authHeader) { + // Successfully got auth header, proceed with session creation + const response = await fetch(api.apiURL('/auth/session'), { + method: 'POST', + credentials: 'include', + headers: { + ...authHeader, + 'Content-Type': 'application/json' + } + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error( + `Failed to create session: ${errorData.message || response.statusText}` + ) + } + + return // Success } - }) - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - throw new Error( - `Failed to create session: ${errorData.message || response.statusText}` - ) + // Exponential backoff before retry (except for last attempt) + if (attempt < 2) { + await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 500)) + } } + + // Failed to get auth header after 3 attempts + throw new Error( + 'No auth header available for session creation after retries' + ) } /** diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 08e441925..0d706f0b2 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -106,10 +106,12 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { } }) - const getIdToken = async (): Promise => { + const getIdToken = async ( + forceRefresh = false + ): Promise => { if (!currentUser.value) return try { - return await currentUser.value.getIdToken() + return await currentUser.value.getIdToken(forceRefresh) } catch (error: unknown) { if ( error instanceof FirebaseError && @@ -135,14 +137,17 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { * 1. Firebase authentication token (if user is logged in) * 2. API key (if stored in the browser's credential manager) * + * @param forceRefresh - If true, forces a refresh of the Firebase token * @returns {Promise} * - A LoggedInAuthHeader with Bearer token if Firebase authenticated * - An ApiKeyAuthHeader with X-API-KEY if API key exists * - null if neither authentication method is available */ - const getAuthHeader = async (): Promise => { + const getAuthHeader = async ( + forceRefresh = false + ): Promise => { // If available, set header with JWT used to identify the user to Firebase service - const token = await getIdToken() + const token = await getIdToken(forceRefresh) if (token) { return { Authorization: `Bearer ${token}`