Skip to content

fix: pass withSupabase error responses through middleware seam - #139

Draft
mandarini wants to merge 5 commits into
mainfrom
fix/error-responses-through-middleware
Draft

fix: pass withSupabase error responses through middleware seam#139
mandarini wants to merge 5 commits into
mainfrom
fix/error-responses-through-middleware

Conversation

@mandarini

@mandarini mandarini commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Middleware entries in withSupabase's middleware array never observed the responses withSupabase builds itself. This PR routes those responses through the array's response phase, with guards.

Scope

Behavior with a response-seam middleware in the array Status
WWW-Authenticate (and any decoration) on auth-generated 401s ✅ Fixed
Decoration of client-construction error responses ✅ Fixed
Silent failure on misplaced pre-auth middleware ✅ Fixed (runtime warning)
Serving OAuth discovery from inside the array ❌ Out of scope (needs pre-auth placement)
Middleware's own OPTIONS/preflight handling from inside the array ❌ Out of scope (needs pre-auth placement)

The wrap form — withOAuthProtectedResource(cfg, withSupabase(cfg, handler)) or pipeline([withOAuthProtectedResource(cfg)], withSupabase(cfg, handler)) — remains the supported placement for the full OAuth flow. A placement support table is added to docs/api-reference.md.

Problem

Auth failures and client-construction failures returned directly, so an entry like withOAuthProtectedResource could not attach WWW-Authenticate to a 401. MCP clients got a bare 401 with no pointer to the authorization server, and nothing failed loudly.

Fix

The middleware chain folds once at wrap time around a dual-purpose terminal: normally it calls the user handler; when the context carries a withSupabase-built error response under a private symbol key, it returns that response so entries run their response phase against it. Folding once means entry state (rate-limit counters, caches) is shared between the success and error paths, so a rate limiter genuinely throttles repeated bad-auth traffic.

Guards bound the semantics:

  • Same-status rule: an entry can decorate an error response but cannot swap it for a different-status one, so a request-phase short-circuit cannot bypass the auth gate.
  • Header restoration: a same-status replacement gets the original error response's headers (CORS, the exposed error-code header) re-applied where the entry set no value of its own; body-derived headers stay the replacement's.
  • Body restoration: an entry that drains the body (logging) gets it rebuilt from a fresh copy; header decorations survive.
  • Crash fallback: an entry that throws on this path is logged via console.error and the plain error response is returned.

On auth failure the context carries userClaims: null and jwtClaims: null, with no auth mode or clients; on client-construction failure the verified claims and auth mode are present and only the clients are absent. The OPTIONS/CORS short-circuit is unchanged, and nothing changes when middleware is absent or empty.

withOAuthProtectedResource warns once per process when it detects (via a private context marker, not key names) that it sits inside a withSupabase array.

Tests

Fourteen new unit tests cover the routing on both error paths, the same-status and crash guards, entry-state persistence across error-path requests, header and body restoration, the context shape on each path, the in-array 401 enrichment, discovery staying wrap-form, and the placement warning (including no false positive from an unrelated userClaims key).

Validation

Tested end-to-end against a real MCP Edge Function in supabase-community/edge-function-mcp-sandbox#11: 401 enrichment works, authenticated MCP calls unaffected, discovery/preflight behave as documented, warning fires.

🤖 Generated with Claude Code

@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@supabase/server@139

commit: 7ede186

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