Skip to content

feat: prefer upstream credentials over JWT propagation for JWKS auth#3061

Draft
joeyorlando wants to merge 3 commits intomainfrom
feat/jwks-credential-resolution
Draft

feat: prefer upstream credentials over JWT propagation for JWKS auth#3061
joeyorlando wants to merge 3 commits intomainfrom
feat/jwks-credential-resolution

Conversation

@joeyorlando
Copy link
Contributor

Summary

  • Flips the credential resolution priority in the MCP gateway so that upstream server credentials (OAuth tokens, PATs, static secrets) take precedence over external IdP JWT propagation when both are available
  • JWT propagation remains as a fallback for end-to-end JWKS patterns where the upstream server validates the same JWT against the IdP's JWKS

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:

  1. Configure an OIDC Identity Provider for JWKS auth
  2. Install upstream MCP servers (GitHub, Jira, etc.) with service credentials
  3. Users authenticate with their org's SSO
  4. Gateway resolves the right upstream token automatically — no JWT propagation to servers that wouldn't understand it

What Changed

In mcp-client.ts getTransportWithKind(), the token resolution priority is now:

  1. Upstream server credentials (secrets.access_token / secrets.raw_access_token) — highest priority
  2. External IdP JWT propagation (tokenAuth.rawToken) — fallback when no upstream credentials exist

Previously the order was reversed (JWT always took priority).

Backward Compatibility

  • End-to-end JWKS pattern (upstream server validates the same JWT): Still works — JWT is propagated when no upstream credentials are configured
  • Existing OAuth/Bearer auth flows: Unaffected — they don't set isExternalIdp

Standalone Example

See the mcp-gateway-token-exchange example for a self-contained demo of the token exchange concept using Keycloak + mock GitHub MCP server.

Test plan

  • Existing e2e test mcp-gateway-jwt-propagation.ee.spec.ts still passes (JWT propagation when no upstream credentials)
  • JWKS auth + upstream server with credentials → upstream token used (not JWT)
  • JWKS auth + upstream server without credentials → JWT propagated (fallback)
  • Non-JWKS auth flows unaffected

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.
@London-Cat
Copy link
Collaborator

London-Cat commented Feb 27, 2026

📊 Reputation Summary

User Rep Pull Requests Activity Assigned Core Reactions
joeyorlando ⚡ 2372 94✅ 4🔄 2❌ 100 issues, 50 comments 4

How is the score calculated? Read about it in the Reputation Bot repository 🤖

@github-actions
Copy link
Contributor

github-actions bot commented Feb 27, 2026

Playwright test results

failed  1 failed
passed  567 passed
flaky  5 flaky

Details

stats  573 tests across 57 suites
duration  4 minutes, 50 seconds
commit  2328c95

Failed tests

api › 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 tests

api › api/chat-settings.spec.ts › Chat API Keys Access Control › member should be able to read chat API keys
api › api/llm-proxy/token-cost-limits.spec.ts › LLMProxy-TokenCostLimits-Gemini › blocks request when profile token cost limit is exceeded
api › api/mcp-catalog-labels.spec.ts › MCP Catalog Labels › labels returned in list endpoint
chromium › ui/chat-settings.spec.ts › Provider Settings - API Keys › Admin can create, update, and delete an API key
chromium › ui/dynamic-credentials.spec.ts › Verify tool calling using dynamic credentials

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.
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.

2 participants