Skip to content

fix(auth,supabase): merge headers case-insensitively - #2581

Open
Sy-D wants to merge 1 commit into
supabase:masterfrom
Sy-D:fix/auth-header-case
Open

fix(auth,supabase): merge headers case-insensitively#2581
Sy-D wants to merge 1 commit into
supabase:masterfrom
Sy-D:fix/auth-header-case

Conversation

@Sy-D

@Sy-D Sy-D commented Jul 29, 2026

Copy link
Copy Markdown

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 fetch joins 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 createClient using the documented global.headers option:

const supabase = createClient(url, anonKey, {
  global: { headers: { authorization: 'Bearer caller' } },
})
await supabase.auth.resetPasswordForEmail('user@example.com')
// sent: authorization: Bearer <anon key>, Bearer caller

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

where what went wrong
applySettingDefaults (supabase-js) merged DEFAULT_HEADERS with global.headers, so a lowercase x-client-info was appended rather than replacing the SDK value
SupabaseClient._initSupabaseAuthClient merged the anon-key Authorization with the caller's headers
auth-js _request / _getRequestParams set Authorization and the API version header by exact case, and spread a Content-Type default the same way

Worth 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-js lib/helpers.ts) and setHeader / hasHeader (auth-js lib/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 Authorization on 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/shared currently holds only tracing, and the two need different semantics from the existing storage-js helper, 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 _request with a captured fetch.
  • packages/core/supabase-js/test/header-case.test.ts — 3 unit tests for mergeHeaders plus 2 that go through createClient and assert what reaches the wire.

Assertions read through new Headers(...) so they see the value the platform actually sends.

On master the behavioural tests fail with the joined values:

case master this branch
lowercase authorization in global.headers Bearer ANONKEY, Bearer caller Bearer caller
lowercase x-client-info in global.headers supabase-js/…, my-app/1.0 my-app/1.0
caller authorization + per-request jwt Bearer caller, Bearer <jwt> Bearer <jwt>
caller content-type + json default application/json;charset=UTF-8, … the caller value

Suites, before vs. after:

suite master this branch
nx test:suite auth-js (Docker-backed GoTrue + Postgres) 20 passed / 20, 553 tests 20 passed / 20, 556 tests
supabase-js jest, excluding integration.test.ts 9 passed suites, 131 tests 10 passed suites, 136 tests

Both deltas are exactly the tests added here; nothing changed status.

nx lint reports 106 errors for auth-js and 0 for supabase-js both here and on master — all pre-existing. nx format:check, nx build auth-js and nx build supabase-js pass.

Known limitations

supabase-js's integration.test.ts fails identically on this branch and on master in 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

  • Bug fix (fix)

Checklist

  • Code formatted (nx format)
  • Unit tests added and passing
  • Integration suite passing (nx test:suite auth-js, fully green)
  • Builds passing (nx build auth-js, nx build supabase-js)
  • Used conventional commits

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.
@Sy-D
Sy-D requested review from a team as code owners July 29, 2026 22:47
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c9b5284a-cbf7-4b13-bc1b-f18829020b2b

📥 Commits

Reviewing files that changed from the base of the PR and between 6331898 and 8816553.

📒 Files selected for processing (6)
  • packages/core/auth-js/src/lib/fetch.ts
  • packages/core/auth-js/src/lib/helpers.ts
  • packages/core/auth-js/test/fetch.test.ts
  • packages/core/supabase-js/src/SupabaseClient.ts
  • packages/core/supabase-js/src/lib/helpers.ts
  • packages/core/supabase-js/test/header-case.test.ts

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved HTTP header handling to avoid duplicate headers caused by differences in capitalization.
    • Preserved caller-provided Content-Type and API version headers.
    • Ensured JWT authorization headers consistently override existing authorization values.
    • Improved global and authentication header merging, including custom authorization and client information headers.
  • Tests
    • Added coverage for case-insensitive header merging, overrides, preservation, and propagation across authentication requests.

Walkthrough

Auth request construction now handles Content-Type, API version, and Authorization headers case-insensitively through new helper utilities. Supabase client header merging also resolves case-insensitive overrides across defaults, global options, and auth initialization. New tests cover header preservation, replacement, casing, and propagation through auth requests.

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
Loading

Possibly related PRs

Suggested labels: auth-js


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant