When to Use It
Use trusted-gateway identity when you have an SSO gateway (Pomerium, oauth2-proxy, Cloudflare Access, Istio/Envoy, Traefik ForwardAuth, or similar) doing authentication and per-route authorization in front of the server, and you don't want the MCP server to also terminate OAuth (which would contend for the single MCP auth handshake).
With this mode enabled, the asserted identity (not a client-supplied value) decides which user's Google credential a request may use. The Google credential is keyed to the verified principal, not to the transport session, so each user gets true per-user isolation even behind a shared proxy.
Keep MCP_ENABLE_OAUTH21=false (the proxy owns the
handshake). TRUST_GATEWAY_IDENTITY is mutually exclusive
with MCP_ENABLE_OAUTH21=true.
Configuration
| Variable | Default | Notes |
|---|---|---|
TRUST_GATEWAY_IDENTITY required | false | Enable trusted-gateway identity mode |
GATEWAY_IDENTITY_JWKS_URL required | - | Proxy JWKS endpoint used to verify the assertion. Must use HTTPS (HTTP is permitted only for loopback development endpoints). |
GATEWAY_IDENTITY_HEADER | x-pomerium-jwt-assertion | Header carrying the JWT (e.g. cf-access-jwt-assertion for Cloudflare Access) |
GATEWAY_IDENTITY_ALGORITHMS | ES256 | Comma-separated allowed signing algorithm(s). Must use asymmetric algorithms from a single family: ES (ES256, ES384, ES512), RS (RS256, RS384, RS512), PS (PS256, PS384, PS512), or EdDSA. Pinned to block alg-confusion and none attacks. |
GATEWAY_IDENTITY_ISSUER | - | If set, the assertion's iss claim must match |
GATEWAY_IDENTITY_AUDIENCE required | - | Assertion aud identifying this MCP deployment; always verified |
The proxy must overwrite or remove any client-supplied identity header
before forwarding its own assertion to the upstream. For Pomerium,
set pass_identity_headers: true on the route.
How It Works
- Verify - the assertion JWT is validated against the
proxy's JWKS (signature +
exp+aud; optionaliss), pinned to the configured algorithm(s). A missing or invalid assertion rejects the request immediately; gateway mode never falls back to bearer tokens or transport-session identity. - Principal - the verified
emailclaim becomesauthenticated_user_email(authenticated_via=gateway_assertion) in request-scoped state only. - No prompt, no spoofing - like OAuth 2.1 mode, the
user_google_emailtool parameter is hidden and auto-filled from the verified principal, so clients never ask for an email and a caller can't act on another account by passing one. Any caller-supplieduser_google_emailis silently dropped (cached pre-gateway schemas keep working), and a configuredUSER_GOOGLE_EMAILdefault is ignored. - Consent enforcement - the per-user Google consent
(side flow) is initiated for the principal. The OAuth state records
an explicit immutable principal binding, and at
/oauth2callbackthe Google account actually consented must match it; a missing binding or mismatch is rejected and nothing is stored.
Credentials still use the normal per-user store (keyed by email); the asserted identity selects and locks which user's grant a request may use.
Provider Examples
Pomerium
Pomerium is the default target. Its identity header
(x-pomerium-jwt-assertion) and algorithm (ES256)
are the built-in defaults, so only the JWKS URL and audience need to be set:
TRUST_GATEWAY_IDENTITY=true
GATEWAY_IDENTITY_JWKS_URL=https://authenticate.example.com/.well-known/pomerium/jwks.json
GATEWAY_IDENTITY_AUDIENCE=workspace-mcp.example.com
# header/alg defaults already target PomeriumCloudflare Access
Cloudflare Access uses RS256 and injects the assertion in cf-access-jwt-assertion. The JWKS URL follows the
pattern https://<team-name>.cloudflareaccess.com/cdn-cgi/access/certs:
TRUST_GATEWAY_IDENTITY=true
GATEWAY_IDENTITY_JWKS_URL=https://<team-name>.cloudflareaccess.com/cdn-cgi/access/certs
GATEWAY_IDENTITY_HEADER=cf-access-jwt-assertion
GATEWAY_IDENTITY_ALGORITHMS=RS256
GATEWAY_IDENTITY_AUDIENCE=<your-access-application-aud>Other proxies
Any proxy that injects a JWKS-verifiable JWT identity header works.
Set GATEWAY_IDENTITY_HEADER to the header name, GATEWAY_IDENTITY_ALGORITHMS to the proxy's signing
algorithm, and point GATEWAY_IDENTITY_JWKS_URL at the
proxy's key-discovery endpoint. oauth2-proxy, Istio/Envoy, and Traefik
ForwardAuth all support this pattern.
Security Notes
- The assertion is verified cryptographically - an unverified, expired, or wrong-algorithm token is rejected.
- Pin
GATEWAY_IDENTITY_ALGORITHMSto your proxy's actual algorithm. Only asymmetric algorithms are accepted (no HMAC), and all configured algorithms must belong to the same family (e.g. all ES or all RS). GATEWAY_IDENTITY_AUDIENCEis mandatory so assertions minted for another application or route cannot be replayed here. SetGATEWAY_IDENTITY_ISSUERas well when your gateway provides a stable issuer.GATEWAY_IDENTITY_JWKS_URLmust use HTTPS. HTTP is permitted only for loopback addresses (localhost / 127.0.0.1) during development.- Configure the proxy to strip or overwrite incoming
GATEWAY_IDENTITY_HEADERvalues from clients. - Ensure the backend is reachable only via the proxy (e.g. ClusterIP, no public port), so the assertion can't be supplied by an untrusted client.