Skip to content

Document gateway JWT for upstream MCP server authentication#623

Open
dshoen619 wants to merge 5 commits into
masterfrom
agent-sec-jwt-upstream-server
Open

Document gateway JWT for upstream MCP server authentication#623
dshoen619 wants to merge 5 commits into
masterfrom
agent-sec-jwt-upstream-server

Conversation

@dshoen619
Copy link
Copy Markdown
Contributor

Summary

Documents the gateway JWT feature added in agent-security#162 and the Redis-backed shared-key follow-up in agent-security#172.

Adds a new Upstream Authentication (Gateway JWT) section to the MCP Gateway architecture page so users understand how the gateway proves request origin to upstream MCP servers, and gives them everything they need to verify on their side.

What's documented

  • Purpose — how the signed X-Gateway-Auth header lets upstream MCP servers reject traffic that didn't flow through the gateway (preventing bypass of authN/authZ/consent)
  • How it works — a sequence diagram showing Ed25519 signing, lazy JWT refresh (re-sign within 30s of expiry), and the optional upstream JWKS verification path
  • JWT claims tableiss, sub, aud, exp, iat, nbf, jti, tenant
  • Two headers coexist — short table clarifying the roles of Authorization: Bearer (upstream OAuth) vs X-Gateway-Auth: Bearer (gateway identity proof)
  • Verification snippet — ~10-line jose example for upstream servers, using createRemoteJWKSet so JWKS caching and key rotation are handled automatically
  • Key rotation note — signing key is cached in Redis (encrypted at rest) and shared across all gateway replicas, so JWTs survive restarts and the JWKS endpoint is consistent across pods; keys rotate on roughly a 90-day cadence
  • ConfigurationGATEWAY_JWT_TTL_SECONDS env var (default 300) and pointer to the platform Settings > Upstream Authentication (JWT) panel

Also updated:

  • System Architecture Overview diagram — Gateway → Upstream edge now labeled Proxy MCP + X-Gateway-Auth JWT, plus a dashed optional verification arrow back to the gateway's JWKS endpoint
  • Data Flow table — Gateway → Upstream MCP row mentions the signed X-Gateway-Auth JWT with an anchor link to the new section

Scope decisions

  • Kept to the architecture page only (not platform or host-setup), since the feature is always-on with no required customer configuration on the gateway — upstream-side verification is the only user-visible surface
  • Intentionally omitted Redis SET NX coordination, AES-256-GCM / KMS internals, and replica-count details — implementation specifics with no troubleshooting value for end users

Test plan

  • Verify the new section renders correctly (mermaid sequence diagram, tables, code block)
  • Confirm the updated System Architecture Overview diagram renders with the new edges
  • Check that the anchor link from the Data Flow table (#upstream-authentication-gateway-jwt) resolves correctly

🤖 Generated with Claude Code

dshoen619 and others added 2 commits April 19, 2026 15:12
Add a new "Upstream Authentication (Gateway JWT)" section to the MCP
Gateway architecture page covering the X-Gateway-Auth header, JWKS
endpoint, JWT claims, verification snippet, and key rotation. Update the
System Architecture Overview and Data Flow table to show the signed JWT
attached to upstream requests and the optional JWKS verification path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Correct the earlier "ephemeral per-pod" framing now that the signing key
is cached in Redis (encrypted at rest) and shared across all gateway
replicas. Note that keys survive restarts, the JWKS endpoint is
consistent across replicas, and keys rotate on roughly a 90-day cadence
so users can reason about JWKS cache lifetimes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 19, 2026

Deploy Preview for permitio-docs ready!

Name Link
🔨 Latest commit 61f4aea
🔍 Latest deploy log https://app.netlify.com/projects/permitio-docs/deploys/6a1eeae5145992000744ae6b
😎 Deploy Preview https://deploy-preview-623--permitio-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@dshoen619 dshoen619 self-assigned this Apr 19, 2026
Copy link
Copy Markdown
Contributor

@zeevmoney zeevmoney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comments.

Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Comment thread docs/permit-mcp-gateway/architecture.mdx Outdated
Fixes 8 inaccuracies vs. the implementation in agent-security main:

- Sequence diagram now shows shared-key load (not ephemeral generation)
- aud row documents canonicalization (lowercase host, strip trailing slashes)
- iat/nbf rows show 30s leeway; adds clock-tolerance note for verifiers
- Drops misleading "enables replay detection" from jti row; adds opt-in
  replay-protection guidance in the verifier section
- Verifier snippet now guards missing/non-string headers and the Bearer
  prefix, and sets clockTolerance: 30
- Key rotation callout: encryption-at-rest is conditional on vault;
  90-day cadence is a warning, not automatic rotation
- Configuration table adds GATEWAY_JWT_REQUIRE_VAULT and TTL bounds

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dshoen619 dshoen619 requested a review from zeevmoney May 4, 2026 09:05
Copy link
Copy Markdown
Contributor

@zeevmoney zeevmoney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Review - the PR looks clean, found nothing.

Copilot AI review requested due to automatic review settings June 2, 2026 14:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds documentation to the MCP Gateway architecture page describing how the gateway authenticates itself to upstream MCP servers using a short-lived, gateway-signed JWT (X-Gateway-Auth) and how upstreams can verify it via the gateway’s JWKS endpoint.

Changes:

  • Updated the System Architecture Overview diagram to label the Gateway → Upstream edge with X-Gateway-Auth and show an optional upstream verification flow to the gateway JWKS endpoint.
  • Updated the Data Flow table to mention the signed X-Gateway-Auth JWT and link to the new section.
  • Added a new “Upstream Authentication (Gateway JWT)” section with flow explanation, claims table, verification snippet, key rotation notes, and configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

end
end

GW->>Up: POST (MCP request)<br/>Authorization: Bearer <upstream_oauth_token><br/>X-Gateway-Auth: Bearer <gateway_jwt>
GW->>GW: Auth + Permit check

rect rgb(240, 248, 255)
Note over GW: JWT signing (per-request)
Comment on lines +497 to +498
| `iat` | `now − 30s` (clock-skew leeway) | When the token was issued |
| `nbf` | `now − 30s` (clock-skew leeway) | Not valid before this time |
| Gateway | Permit.io | HTTPS | Authorization checks (Cloud PDP) |
| Consent Service | Permit.io | HTTPS | Policy sync during consent |
| Gateway | Upstream MCP | HTTPS | Streamable HTTP with upstream OAuth tokens |
| Gateway | Upstream MCP | HTTPS | Streamable HTTP with upstream OAuth tokens + signed `X-Gateway-Auth` JWT (see [Upstream Authentication](#upstream-authentication-gateway-jwt)) |
:::

:::info Key rotation
The signing key is stored in Redis and shared across all gateway replicas, so JWTs remain valid across pod restarts and the JWKS endpoint returns the same key regardless of which replica serves it. When the token vault is enabled (`VAULT_ENABLED=true` + `AWS_KMS_KEY_ID`), the key is encrypted at rest using AES-256-GCM with AWS KMS envelope encryption; without vault it is stored as plaintext JSON (the gateway logs a warning). Set `GATEWAY_JWT_REQUIRE_VAULT=true` in production to refuse the plaintext fallback.
Copy link
Copy Markdown
Contributor

@zeevmoney zeevmoney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments.

Note over GW: JWT signing (per-request)
GW->>GW: Is cached JWT still valid?
alt JWT expired or near expiry (within 30s)
GW->>GW: Sign fresh JWT<br/>{iss, sub, aud, exp, iat, jti, tenant}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sequence-diagram claim list omits nbf

The note enumerates the signed claims as {iss, sub, aud, exp, iat, jti, tenant} but omits nbf, which the JWT claims table below (the nbf row) and the implementation both include (gateway/src/clients/gateway_jwt.rs sets nbf = now - NBF_LEEWAY_SECS). Because the note explicitly lists each claim, the omission reads as if nbf isn't signed.

Suggestion: Add nbf to the list:

GW->>GW: Sign fresh JWT<br/>{iss, sub, aud, exp, iat, nbf, jti, tenant}

| Gateway | Permit.io | HTTPS | Authorization checks (Cloud PDP) |
| Consent Service | Permit.io | HTTPS | Policy sync during consent |
| Gateway | Upstream MCP | HTTPS | Streamable HTTP with upstream OAuth tokens |
| Gateway | Upstream MCP | HTTPS | Streamable HTTP with upstream OAuth tokens + signed `X-Gateway-Auth` JWT (see [Upstream Authentication](#upstream-authentication-gateway-jwt)) |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data Flow diagram edge not updated to match this table row

This row now mentions the signed X-Gateway-Auth JWT, but the Data Flow diagram edge a few lines above (the GW -->|"Streamable HTTP +<br/>upstream OAuth tokens"| MCP edge) was not updated, so the diagram and this table disagree.

(Copilot raised the same disagreement on this line; the actual fix is on that diagram edge.)

Suggestion: update the diagram edge to match:

GW -->|"Streamable HTTP + upstream OAuth tokens<br/>+ X-Gateway-Auth JWT"| MCP

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.

3 participants