Skip to content

event.setHeaders() throws "header is already set" when signInWithOtp() writes PKCE verifier cookie in SvelteKit #279

Description

@decasdev

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

Calling supabase.auth.signInWithOtp() from a SvelteKit form action, using the standard SvelteKit SSR cookie-handling pattern documented at https://supabase.com/docs/guides/auth/server-side/creating-a-client, throws a 500 error:

Error: "Cache-Control" header is already set at Object.setHeaders (node_modules/@sveltejs/kit/src/runtime/server/respond.js) at setAll (src/hooks.server.ts) at applyServerStorage (node_modules/@supabase/ssr/dist/main/cookies.js) at async Object.setItem (node_modules/@supabase/ssr/dist/main/cookies.js) at async setItemAsync (node_modules/@supabase/auth-js/dist/main/lib/helpers.js) at async storePKCEVerifier (node_modules/@supabase/auth-js/dist/main/lib/helpers.js) at async getCodeChallengeAndMethod (node_modules/@supabase/auth-js/dist/main/lib/helpers.js) at async SupabaseAuthClient.signInWithOtp (node_modules/@supabase/auth-js/dist/main/GoTrueClient.js)

Root cause appears to be: storing the PKCE code verifier during signInWithOtp() invokes the setAll cookie callback, which calls event.setHeaders(headers) with a Cache-Control header (per @supabase/ssr's own documented behavior of attaching cache-prevention headers to cookie-writing calls). This callback gets invoked more than once within the single signInWithOtp() call. SvelteKit's event.setHeaders() throws if called twice with the same header name in one request — unlike, e.g., a plain Headers.set(), which would just overwrite silently. So this specific combination (SvelteKit's stricter header API + @supabase/ssr calling the headers callback multiple times per auth operation) crashes.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Set up SvelteKit with @supabase/ssr, following the exact hooks.server.ts pattern from the official docs (createServerClient with getAll/setAll, calling event.setHeaders(headers) inside setAll).
  2. Call supabase.auth.signInWithOtp({ email }) from a form action using the request-scoped server client from event.locals.
  3. Submit the form.
  4. See 500 error / stack trace above.

Expected behavior

signInWithOtp() (or any auth method that triggers cookie/header writes as a side effect, e.g. PKCE verifier storage) should not cause the setAll headers argument to be applied more than once per header name within a single request — either by not invoking the headers callback redundantly, or by documenting that consuming frameworks with strict single-set header semantics (like SvelteKit) need to dedupe headers themselves in their setAll implementation.

System information

  • OS: macOS
  • Version of supabase-js: 2.111.0
  • Version of @supabase/ssr: 0.12.4 (latest as of testing)
  • Version of Node.js: v25.2.1
  • SvelteKit: 2.70.2

Additional context

Related to a similar class of issue: #144 (duplicate Set-Cookie/header calls per request), though that issue concerns multiple client instances in one request, whereas this occurs with a single client instance during a single signInWithOtp() call.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions