Bug Report
Describe the bug
When a session is large enough to require multiple cookie chunks (3+ chunks), cookie chunk corruption can cause a TypeError: Cannot create property 'user' on string '...' in GoTrueClient._recoverAndRefresh.
Error
TypeError: Cannot create property 'user' on string '{"access_token":"eyJhbGciOiJIUzI....", ...}'
at SupabaseAuthClient._recoverAndRefresh (GoTrueClient.ts:2612:32)
at SupabaseAuthClient._initialize (GoTrueClient.ts:544:7)
Root Cause Analysis
The issue involves a chain across @supabase/ssr and @supabase/auth-js:
-
Large session requires 3 cookie chunks: My session JSON is ~5KB (user has 4 linked identities with large metadata). After base64url encoding (~6.7KB), this exceeds MAX_CHUNK_SIZE (3180) and gets split into 3 cookie chunks (.0, .1, .2).
-
Cookie chunks become corrupted: During a token refresh on the server, applyServerStorage calls setAll to update all cookie chunks. If the response has already been committed (common in SSR frameworks), the Set-Cookie headers are not sent to the browser. The browser retains a mix of old and new chunk data.
-
Corrupted base64url → invalid JSON: On the next request, combineChunks concatenates the mismatched chunks. The base64url-decoded result is a corrupted JSON string that fails JSON.parse.
-
getItemAsync returns raw string on parse failure:
// @supabase/auth-js/src/lib/helpers.ts
export const getItemAsync = async (storage, key) => {
const value = await storage.getItem(key)
if (!value) return null
try {
return JSON.parse(value)
} catch {
return value // ← returns the raw string when JSON is invalid
}
}
-
_recoverAndRefresh crashes: currentSession is now a string. Since typeof "..." .user === undefined, the code enters the else if (currentSession && !currentSession.user) branch and attempts currentSession.user = userNotAvailableProxy(), which throws TypeError: Cannot create property 'user' on string.
-
Self-reinforcing: If error handling anywhere re-saves currentSession (a string) via setItemAsync, it gets double-JSON-encoded (JSON.stringify(string) wraps it in quotes), making the corruption permanent until cookies are manually cleared.
Suggested Fixes
In @supabase/auth-js (_recoverAndRefresh): Validate that currentSession is actually an object after getItemAsync, not a string:
const currentSession = await getItemAsync(this.storage, this.storageKey)
if (currentSession && typeof currentSession !== 'object') {
// Corrupted session data, clear it
await this._removeSession()
return
}
In @supabase/ssr: Consider adding integrity validation when combining chunks — e.g., verify the decoded value is valid JSON before returning it, and return null if not (so auth-js treats it as "no session" rather than crashing).
To Reproduce
- Have a Supabase user with multiple linked identities (Apple, Google, email) — this creates a large session (~5KB JSON)
- Use
createServerClient with cookie-based storage
- Trigger a token refresh during a server request where the response is committed before
setAll runs
- On the next request, the corrupted cookie chunks cause the TypeError
Expected Behavior
The library should gracefully handle corrupted/invalid session data from cookies — treat it as "no session" and clear the bad cookies, rather than crashing with a TypeError.
Additional Context
Session size breakdown:
- Session JSON: ~5065 bytes
- Base64url encoded: ~6761 bytes
- Cookie chunks needed: 3 (MAX_CHUNK_SIZE = 3180)
The large session is due to 4 linked identities with substantial identity_data including avatar URLs and OAuth metadata.
Bug Report
Describe the bug
When a session is large enough to require multiple cookie chunks (3+ chunks), cookie chunk corruption can cause a
TypeError: Cannot create property 'user' on string '...'inGoTrueClient._recoverAndRefresh.Error
Root Cause Analysis
The issue involves a chain across
@supabase/ssrand@supabase/auth-js:Large session requires 3 cookie chunks: My session JSON is ~5KB (user has 4 linked identities with large metadata). After base64url encoding (~6.7KB), this exceeds
MAX_CHUNK_SIZE(3180) and gets split into 3 cookie chunks (.0,.1,.2).Cookie chunks become corrupted: During a token refresh on the server,
applyServerStoragecallssetAllto update all cookie chunks. If the response has already been committed (common in SSR frameworks), theSet-Cookieheaders are not sent to the browser. The browser retains a mix of old and new chunk data.Corrupted base64url → invalid JSON: On the next request,
combineChunksconcatenates the mismatched chunks. The base64url-decoded result is a corrupted JSON string that failsJSON.parse.getItemAsyncreturns raw string on parse failure:_recoverAndRefreshcrashes:currentSessionis now a string. Sincetypeof "..." .user === undefined, the code enters theelse if (currentSession && !currentSession.user)branch and attemptscurrentSession.user = userNotAvailableProxy(), which throwsTypeError: Cannot create property 'user' on string.Self-reinforcing: If error handling anywhere re-saves
currentSession(a string) viasetItemAsync, it gets double-JSON-encoded (JSON.stringify(string)wraps it in quotes), making the corruption permanent until cookies are manually cleared.Suggested Fixes
In
@supabase/auth-js(_recoverAndRefresh): Validate thatcurrentSessionis actually an object aftergetItemAsync, not a string:In
@supabase/ssr: Consider adding integrity validation when combining chunks — e.g., verify the decoded value is valid JSON before returning it, and returnnullif not (so auth-js treats it as "no session" rather than crashing).To Reproduce
createServerClientwith cookie-based storagesetAllrunsExpected Behavior
The library should gracefully handle corrupted/invalid session data from cookies — treat it as "no session" and clear the bad cookies, rather than crashing with a TypeError.
Additional Context
Session size breakdown:
The large session is due to 4 linked identities with substantial
identity_dataincluding avatar URLs and OAuth metadata.