Skip to content

Commit ae2f516

Browse files
committed
feat: add retry logic for session cookie creation with token refresh
- Add retry mechanism with exponential backoff in useSessionCookie - Support forceRefresh parameter in getAuthHeader and getIdToken - First attempt uses cached token, retries force token refresh - Fixes intermittent 'No auth header available' errors during token refresh Addresses Sentry issue affecting users with authentication timing issues
1 parent 704de20 commit ae2f516

File tree

2 files changed

+42
-20
lines changed

2 files changed

+42
-20
lines changed

src/platform/auth/session/useSessionCookie.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,49 @@ export const useSessionCookie = () => {
1010
/**
1111
* Creates or refreshes the session cookie.
1212
* Called after login and on token refresh.
13+
* Implements retry logic with token refresh for handling timing issues.
1314
*/
1415
const createSession = async (): Promise<void> => {
1516
if (!isCloud) return
1617

1718
const authStore = useFirebaseAuthStore()
18-
const authHeader = await authStore.getAuthHeader()
1919

20-
if (!authHeader) {
21-
throw new Error('No auth header available for session creation')
22-
}
20+
// Simple retry with forceRefresh for token timing issues
21+
for (let attempt = 0; attempt < 3; attempt++) {
22+
// First attempt uses cached token, retries force refresh
23+
const authHeader = await authStore.getAuthHeader(attempt > 0)
2324

24-
const response = await fetch(api.apiURL('/auth/session'), {
25-
method: 'POST',
26-
credentials: 'include',
27-
headers: {
28-
...authHeader,
29-
'Content-Type': 'application/json'
25+
if (authHeader) {
26+
// Successfully got auth header, proceed with session creation
27+
const response = await fetch(api.apiURL('/auth/session'), {
28+
method: 'POST',
29+
credentials: 'include',
30+
headers: {
31+
...authHeader,
32+
'Content-Type': 'application/json'
33+
}
34+
})
35+
36+
if (!response.ok) {
37+
const errorData = await response.json().catch(() => ({}))
38+
throw new Error(
39+
`Failed to create session: ${errorData.message || response.statusText}`
40+
)
41+
}
42+
43+
return // Success
3044
}
31-
})
3245

33-
if (!response.ok) {
34-
const errorData = await response.json().catch(() => ({}))
35-
throw new Error(
36-
`Failed to create session: ${errorData.message || response.statusText}`
37-
)
46+
// Exponential backoff before retry (except for last attempt)
47+
if (attempt < 2) {
48+
await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 500))
49+
}
3850
}
51+
52+
// Failed to get auth header after 3 attempts
53+
throw new Error(
54+
'No auth header available for session creation after retries'
55+
)
3956
}
4057

4158
/**

src/stores/firebaseAuthStore.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,12 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
106106
}
107107
})
108108

109-
const getIdToken = async (): Promise<string | undefined> => {
109+
const getIdToken = async (
110+
forceRefresh = false
111+
): Promise<string | undefined> => {
110112
if (!currentUser.value) return
111113
try {
112-
return await currentUser.value.getIdToken()
114+
return await currentUser.value.getIdToken(forceRefresh)
113115
} catch (error: unknown) {
114116
if (
115117
error instanceof FirebaseError &&
@@ -135,14 +137,17 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
135137
* 1. Firebase authentication token (if user is logged in)
136138
* 2. API key (if stored in the browser's credential manager)
137139
*
140+
* @param forceRefresh - If true, forces a refresh of the Firebase token
138141
* @returns {Promise<AuthHeader | null>}
139142
* - A LoggedInAuthHeader with Bearer token if Firebase authenticated
140143
* - An ApiKeyAuthHeader with X-API-KEY if API key exists
141144
* - null if neither authentication method is available
142145
*/
143-
const getAuthHeader = async (): Promise<AuthHeader | null> => {
146+
const getAuthHeader = async (
147+
forceRefresh = false
148+
): Promise<AuthHeader | null> => {
144149
// If available, set header with JWT used to identify the user to Firebase service
145-
const token = await getIdToken()
150+
const token = await getIdToken(forceRefresh)
146151
if (token) {
147152
return {
148153
Authorization: `Bearer ${token}`

0 commit comments

Comments
 (0)