fix(auth,supabase): merge headers case-insensitively - #2581
Conversation
Header names are case-insensitive (RFC 9110), but an object spread only
overrides on an exact key match, so two entries differing only in case
both survive and `fetch` joins them into one comma-separated value — a
malformed header rather than the intended override.
Reachable straight from `createClient`. With
`global.headers: { authorization: 'Bearer caller' }`, every auth request
went out as:
authorization: Bearer <anon key>, Bearer caller
Three merge sites were affected:
- `applySettingDefaults` merged `DEFAULT_HEADERS` with `global.headers`,
so a lowercase `x-client-info` was appended to the SDK's own value
instead of replacing it.
- `SupabaseClient._initSupabaseAuthClient` merged the anon-key
`Authorization` with the caller's headers. The
`hasCustomAuthorizationHeader` flag right below it already compared
case-insensitively, so the two disagreed.
- `auth-js` `_request` set `Authorization` and the API version header by
exact case, and `_getRequestParams` spread a `Content-Type` default the
same way.
`mergeHeaders` (supabase-js) and `setHeader`/`hasHeader` (auth-js) drop
any existing case variant before writing. Both deliberately keep the
spelling of the winning source rather than lowercasing, so the header
names these packages put on the wire are unchanged — the passkey tests
assert `Authorization` capitalized and still pass untouched.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAuth request construction now handles Sequence Diagram(s)sequenceDiagram
participant SupabaseClient
participant SupabaseAuthClient
participant AuthRequest
participant Fetch
SupabaseClient->>SupabaseAuthClient: initialize with merged headers
SupabaseAuthClient->>AuthRequest: create auth request
AuthRequest->>Fetch: send case-insensitively resolved headers
Possibly related PRs
Suggested labels: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Description
Header names are case-insensitive (RFC 9110), but an object spread only overrides on an exact key match. Two entries differing only in case both survive the merge, and
fetchjoins same-name headers into one comma-separated value — so the result is a malformed header rather than the intended override.This is reachable straight from
createClientusing the documentedglobal.headersoption:Every auth request goes out with an Authorization header no server accepts. A caller who spells the header lowercase has no way to override it.
The same happens to
x-client-info, where the SDK version string is prepended to the caller's value.Three merge sites
applySettingDefaults(supabase-js)DEFAULT_HEADERSwithglobal.headers, so a lowercasex-client-infowas appended rather than replacing the SDK valueSupabaseClient._initSupabaseAuthClientAuthorizationwith the caller's headersauth-js_request/_getRequestParamsAuthorizationand the API version header by exact case, and spread aContent-Typedefault the same wayWorth noting the second one:
hasCustomAuthorizationHeader, two lines below that merge, already compares case-insensitively. The flag and the merge disagreed, so the header broke for exactly the callers the flag exists to support.What changed
mergeHeaders(supabase-jslib/helpers.ts) andsetHeader/hasHeader(auth-jslib/helpers.ts) drop any existing case variant before writing.Both keep the spelling of the winning source rather than lowercasing everything. That matters: the passkey tests assert a capitalized
Authorizationon the wire, and they pass here untouched. My first attempt normalized to lowercase and broke 5 of them — the header names these packages emit are observable, so this version leaves them alone.The helpers are per-package rather than shared:
packages/sharedcurrently holds onlytracing, and the two need different semantics from the existingstorage-jshelper, which lowercases. Happy to consolidate if you would rather have one.Testing
packages/core/auth-js/test/fetch.test.ts— 3 tests added to the existing file, driving_requestwith a captured fetch.packages/core/supabase-js/test/header-case.test.ts— 3 unit tests formergeHeadersplus 2 that go throughcreateClientand assert what reaches the wire.Assertions read through
new Headers(...)so they see the value the platform actually sends.On
masterthe behavioural tests fail with the joined values:masterauthorizationinglobal.headersBearer ANONKEY, Bearer callerBearer callerx-client-infoinglobal.headerssupabase-js/…, my-app/1.0my-app/1.0authorization+ per-request jwtBearer caller, Bearer <jwt>Bearer <jwt>content-type+ json defaultapplication/json;charset=UTF-8, …Suites, before vs. after:
masternx test:suite auth-js(Docker-backed GoTrue + Postgres)supabase-jsjest, excludingintegration.test.tsBoth deltas are exactly the tests added here; nothing changed status.
nx lintreports 106 errors forauth-jsand 0 forsupabase-jsboth here and onmaster— all pre-existing.nx format:check,nx build auth-jsandnx build supabase-jspass.Known limitations
supabase-js'sintegration.test.tsfails identically on this branch and onmasterin my environment (24 tests) — it needs a Supabase stack that the auth-js test stack does not provide, so I compared with it excluded rather than claiming a green run.This is one PR across two packages because it is a single bug on a single request path, and the repo notes cross-library fixes belong in one PR. Happy to split it if you prefer.
Type of Change
Checklist
nx format)nx test:suite auth-js, fully green)nx build auth-js,nx build supabase-js)