Description
The mcp-auth policy reads and validates the client's downstream Authorization header (delegating to jwt-auth), then removes that header before the request is forwarded upstream. The removal is emitted during the request-body phase (mcp-auth OnRequestBody copies jwt-auth's HeadersToRemove=["authorization"] into its UpstreamRequestModifications).
If a set-headers policy is also configured to set an upstream Authorization header, that value is set during the request-header phase. The kernel runs every policy's header phase before any policy's body phase, so the mcp-auth body-phase removal always executes after the set-headers header-phase set. The net result is that the backend receives no Authorization header, even though set-headers was configured to inject one.
Concrete scenario:
mcp-auth validates the client's Authorization header (downstream credential).
set-headers sets Authorization to a backend credential to be forwarded upstream.
- Expected: client credential is validated and stripped; backend receives the
set-headers value.
- Actual: backend receives no
Authorization header — the set-headers value is dropped by mcp-auth's body-phase removal.
Key point: this is phase-based, not order-based. Reordering the two policies does not help, because set-headers always runs in the header phase and mcp-auth's removal always runs in the body phase — the removal always wins.
Note: validation itself is unaffected — mcp-auth/jwt-auth validate against the Downstream snapshot (the client's real header), so a peer policy rewriting the live header does not break validation. Only the removal step is buggy.
Note: today the Authorization removal is driven by jwt-auth's forwardToken default (true), which relocates the token to x-forwarded-authorization and removes the original Authorization. Even forwardToken=false removes it. So there is no existing setting that says "validate but leave Authorization untouched for a downstream policy to own." Additionally, mcp-auth does not currently expose forwardToken at all — it falls through to jwt-auth's default via a maps.Clone(params) pass-through — so users cannot opt out.
Proposed fix
Introduce a forwardToken param on mcp-auth — the same name as jwt-auth's param, but as an enum (not a bool) with these values:
send — forward the validated token (equivalent to jwt-auth's forwardToken=true).
drop — remove the Authorization header entirely (equivalent to jwt-auth's forwardToken=false).
auto (default) — only remove the header if, at body phase, the live Authorization value still equals the Downstream snapshot value (i.e. no peer policy rewrote it). If a peer policy (e.g. set-headers) injected a different value, preserve it.
auto is a genuinely new third behavior that neither jwt-auth nor mcp-auth has today, and it fixes the collision described above out of the box while keeping the secure default of stripping the client credential when nothing else claims the header.
While implementing, confirm the interaction with jwt-auth's existing forwardToken / forwardedTokenHeader (default x-forwarded-authorization) path so the same token is not double-handled.
Exposing the remaining jwt-auth forwarding params (headerName, forwardedTokenHeader, forwardTokenStripScheme) on mcp-auth for consistency is tracked separately in #2867.
Version
mcp-auth v1.1.2
Related Issue
#2867
Description
The
mcp-authpolicy reads and validates the client's downstreamAuthorizationheader (delegating tojwt-auth), then removes that header before the request is forwarded upstream. The removal is emitted during the request-body phase (mcp-authOnRequestBodycopiesjwt-auth'sHeadersToRemove=["authorization"]into itsUpstreamRequestModifications).If a
set-headerspolicy is also configured to set an upstreamAuthorizationheader, that value is set during the request-header phase. The kernel runs every policy's header phase before any policy's body phase, so themcp-authbody-phase removal always executes after theset-headersheader-phase set. The net result is that the backend receives noAuthorizationheader, even thoughset-headerswas configured to inject one.Concrete scenario:
mcp-authvalidates the client'sAuthorizationheader (downstream credential).set-headerssetsAuthorizationto a backend credential to be forwarded upstream.set-headersvalue.Authorizationheader — theset-headersvalue is dropped bymcp-auth's body-phase removal.Key point: this is phase-based, not order-based. Reordering the two policies does not help, because
set-headersalways runs in the header phase andmcp-auth's removal always runs in the body phase — the removal always wins.Note: validation itself is unaffected —
mcp-auth/jwt-authvalidate against theDownstreamsnapshot (the client's real header), so a peer policy rewriting the live header does not break validation. Only the removal step is buggy.Note: today the
Authorizationremoval is driven byjwt-auth'sforwardTokendefault (true), which relocates the token tox-forwarded-authorizationand removes the originalAuthorization. EvenforwardToken=falseremoves it. So there is no existing setting that says "validate but leaveAuthorizationuntouched for a downstream policy to own." Additionally,mcp-authdoes not currently exposeforwardTokenat all — it falls through tojwt-auth's default via amaps.Clone(params)pass-through — so users cannot opt out.Proposed fix
Introduce a
forwardTokenparam onmcp-auth— the same name asjwt-auth's param, but as an enum (not a bool) with these values:send— forward the validated token (equivalent tojwt-auth'sforwardToken=true).drop— remove theAuthorizationheader entirely (equivalent tojwt-auth'sforwardToken=false).auto(default) — only remove the header if, at body phase, the liveAuthorizationvalue still equals theDownstreamsnapshot value (i.e. no peer policy rewrote it). If a peer policy (e.g.set-headers) injected a different value, preserve it.autois a genuinely new third behavior that neitherjwt-authnormcp-authhas today, and it fixes the collision described above out of the box while keeping the secure default of stripping the client credential when nothing else claims the header.While implementing, confirm the interaction with
jwt-auth's existingforwardToken/forwardedTokenHeader(defaultx-forwarded-authorization) path so the same token is not double-handled.Exposing the remaining
jwt-authforwarding params (headerName,forwardedTokenHeader,forwardTokenStripScheme) onmcp-authfor consistency is tracked separately in #2867.Version
mcp-auth v1.1.2
Related Issue
#2867