Skip to content

TypeError in _recoverAndRefresh when large session cookie chunks become corrupted #169

@wong2

Description

@wong2

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:

  1. 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).

  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.

  3. 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.

  4. 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
      }
    }
  5. _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.

  6. 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

  1. Have a Supabase user with multiple linked identities (Apple, Google, email) — this creates a large session (~5KB JSON)
  2. Use createServerClient with cookie-based storage
  3. Trigger a token refresh during a server request where the response is committed before setAll runs
  4. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions