feat: prefer upstream credentials over JWT propagation for JWKS auth#3061
feat: prefer upstream credentials over JWT propagation for JWKS auth#3061joeyorlando wants to merge 3 commits intomainfrom
Conversation
When a user authenticates via external IdP JWKS, the gateway previously always propagated the IdP JWT to upstream MCP servers. This meant upstream servers like GitHub or Jira (which need their own service-specific tokens) couldn't be used with JWKS auth. This flips the credential resolution priority so that upstream server credentials (OAuth tokens, PATs) take precedence when available. JWT propagation remains as a fallback for end-to-end JWKS patterns where the upstream server validates the same JWT. This enables a powerful workflow: admins install MCP servers with service credentials once, users authenticate via their org's IdP (Keycloak, Okta, etc.), and the gateway automatically resolves the right upstream token.
📊 Reputation Summary
How is the score calculated? Read about it in the Reputation Bot repository 🤖 |
Playwright test resultsDetails
Failed testsapi › api/llm-proxy/virtual-api-keys.spec.ts › Virtual API Keys - LLM Proxy › virtual key with per-key base URL routes to custom endpoint Flaky testsapi › api/chat-settings.spec.ts › Chat API Keys Access Control › member should be able to read chat API keys |
Add 6 unit tests covering credential resolution when using External IdP JWKS authentication: - Upstream access_token takes priority over JWT propagation (remote) - Falls back to JWT propagation when no upstream credentials (remote) - raw_access_token takes priority over JWT propagation (remote) - Non-JWKS auth (OAuth/Bearer) continues to use upstream credentials - Dynamic credentials resolve and use server credentials with JWKS - Local streamable-http servers use credentials over JWT propagation Also update mcp-authentication.md to document the credential resolution priority: upstream server credentials are preferred, with JWT propagation as a fallback for the end-to-end JWKS pattern.
Add WireMock stubs (echo_auth tool) and two Playwright e2e tests that verify credential resolution when authenticating via External IdP JWKS: 1. When upstream server has credentials configured, they should be preferred over the caller's JWT — the WireMock echo confirms the static token is forwarded, not the Keycloak JWT. 2. When no upstream credentials exist, the JWT should be propagated as a fallback (end-to-end JWKS pattern) — the WireMock echo confirms a JWT-shaped token is forwarded.
Summary
Motivation
When a user authenticates via external IdP JWKS (e.g., Keycloak, Okta), the gateway previously always forwarded the IdP JWT to upstream MCP servers. This blocked a key use case: using JWKS auth with upstream servers that require their own service-specific tokens (GitHub PATs, Jira API tokens, etc.).
With this change, an admin can:
What Changed
In
mcp-client.tsgetTransportWithKind(), the token resolution priority is now:secrets.access_token/secrets.raw_access_token) — highest prioritytokenAuth.rawToken) — fallback when no upstream credentials existPreviously the order was reversed (JWT always took priority).
Backward Compatibility
isExternalIdpStandalone Example
See the
mcp-gateway-token-exchangeexample for a self-contained demo of the token exchange concept using Keycloak + mock GitHub MCP server.Test plan
mcp-gateway-jwt-propagation.ee.spec.tsstill passes (JWT propagation when no upstream credentials)