ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Getting Started
Concepts
API Management
AI Gateway
MCP Gateway
    Overview
    Getting Started
      How it works
      Connect MCP clients
      Authentication
        OverviewUpstream OAuthConnect an upstream OAuth providerConnect an API-key upstream
        Identity providers
        Manual OAuth testing
      Cross App Access
      Configuration
      Policies
      Observability
      ReferenceServer RegistryTroubleshooting
    MCP Server
    Developer Portal
    Development
    Deploying & Source Control
    Analytics
    Observability
    Networking & Infrastructure
    Account Management
    Programming API
    Build with AI
    Zuplo CLI
    Migration Guides
    Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
    powered by Zuplo
    Authentication

    Authentication overview

    The Zuplo MCP Gateway sits in the middle of two independent OAuth relationships. MCP clients connect to the gateway and authenticate against it. The gateway, in turn, connects to each upstream MCP server and authenticates against it on the user's behalf. Both halves follow the same spec — the MCP authorization model at revision 2025-11-25 — but they're configured separately and use different policies.

    This page explains both layers, the standards involved, and the moving parts (sessions, scopes, token lifetimes) you'll see throughout the gateway. The identity provider catalog below points at per-IdP setup guides; the upstream side has its own pages:

    • Per-user OAuth to upstream MCP servers — the upstream side, conceptually
    • Connect a gateway to an upstream OAuth provider — how-to
    • Manual OAuth testing — how-to

    The two layers

    Every authenticated MCP request involves two distinct OAuth surfaces.

    Downstream: gateway as OAuth server

    When a client like Claude Desktop, Cursor, or Claude Code connects to a /mcp/{slug} route on the gateway, the client is the OAuth client and the gateway is both the OAuth 2.1 Resource Server (RS) and the OAuth 2.1 Authorization Server (AS).

    The gateway publishes everything an MCP client needs to discover and complete an OAuth flow:

    • An RFC 9728 Protected Resource Metadata document per route.
    • An RFC 8414 Authorization Server Metadata document, both gateway-wide and per route.
    • An RFC 7591 Dynamic Client Registration endpoint.
    • An OAuth Client ID Metadata Document (CIMD) acceptor.
    • /__zuplo/oauth/authorize, /__zuplo/oauth/token, /__zuplo/oauth/revoke, and /__zuplo/oauth/callback endpoints.

    Browser identity is delegated to an OIDC identity provider you configure — Auth0, Okta, or any OIDC discovery-compatible IdP. The IdP authenticates the user; the gateway then issues its own bearer access token to the MCP client. The IdP's token never reaches the MCP client.

    Upstream: gateway as OAuth client

    When the gateway forwards a request to an upstream MCP server (Linear, Stripe, Notion, GitHub, your internal service, and so on), the gateway is the OAuth client and the upstream MCP server is the resource server. On behalf of each user, the gateway runs through the upstream provider's OAuth discovery, registers itself (preferring OIDC Client ID Metadata Documents, falling back to RFC 7591 Dynamic Client Registration), redirects the user through the upstream /authorize, captures the upstream tokens, and stores them encrypted at rest.

    On subsequent MCP requests, the gateway resolves the stored upstream credential per user, refreshes it if necessary, and injects it as an Authorization: Bearer ... header when proxying to the upstream.

    Token passthrough is forbidden

    The MCP authorization spec explicitly forbids forwarding an inbound bearer token to an upstream API. Inbound auth headers don't leak to the upstream — the gateway always uses an independent upstream credential. The gateway-issued token a client presents and the upstream token the gateway forwards are never the same token.

    Standards observed

    The gateway implements the following standards in their MCP-mandated subsets.

    StandardPurpose
    OAuth 2.1 (draft)Core authorization framework.
    RFC 7636 — PKCERequired on every authorization code flow. S256 is required when technically capable.
    RFC 8414 — Authorization Server MetadataPublished at /.well-known/oauth-authorization-server[/{routePath}].
    OpenID Connect Discovery 1.0Accepted alongside RFC 8414 as authorization-server discovery (added in the 2025-11-25 MCP revision).
    RFC 9728 — Protected Resource MetadataPublished at /.well-known/oauth-protected-resource/{routePath} per MCP route.
    RFC 7591 — Dynamic Client RegistrationAccepted at /__zuplo/oauth/register.
    OAuth Client ID Metadata Documents (CIMD)Recommended client identification path per the 2025-11-25 MCP revision. The gateway advertises client_id_metadata_document_supported: true and accepts URLs as client_id values when CIMD is enabled.
    RFC 8707 — Resource IndicatorsMCP clients MUST include the resource parameter on every authorization and token request. The gateway validates that incoming bearer tokens were minted for the route's canonical resource URI.
    RFC 6750 — Bearer tokensAuthorization: Bearer ... only, header position only — tokens in query strings are rejected.
    RFC 7009 — Token RevocationPublished at /__zuplo/oauth/revoke.

    CIMD is the recommended client identification path going forward; DCR is retained for backwards compatibility with older MCP clients. Both work against the same /__zuplo/oauth/register and AS metadata surface — clients that support either are accommodated.

    Downstream flow

    The downstream OAuth flow follows the spec's authorization-code grant with PKCE plus the MCP resource parameter binding.

    Zuplo Gateway
    OAuth endpoints
    MCP route
    MCP Client
    Identity Provider
    Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
    Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

    The flow is the standard MCP authorization handshake. The McpGatewayPlugin registers the /.well-known/... and /__zuplo/oauth/... endpoints automatically.

    Upstream flow

    The first request to a route whose upstream needs OAuth produces a connect-required error. The MCP client is expected to surface the returned URL to the user; the user completes upstream OAuth in a browser; the next MCP request succeeds.

    Zuplo Gateway
    Upstream connect
    MCP route
    MCP Client
    Upstream Provider
    Upstream MCP server
    Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
    Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

    The connect-required JSON-RPC error wraps an MCP UrlElicitationRequiredError, so clients that implement the URL-elicitation extension open the URL in a browser automatically. Older clients surface the URL as text for the user to open manually.

    When the gateway has a stored upstream connection for the user, no connect-required error is returned — the proxy forwards transparently.

    Identity providers

    Use a provider-specific policy from the table, or the generic mcp-oauth-inbound policy for other OIDC providers. Attach the same policy to every MCP route in the project. Attaching different MCP OAuth policies causes a configuration error.

    Each wrapper has the same shape: it takes a small set of provider-specific fields (a domain, a tenant ID, a subdomain, and so on) plus clientId and clientSecret, and derives the OIDC issuer, JWKS URL, and browser-login endpoints automatically. Under the hood every wrapper composes the generic mcp-oauth-inbound policy with provider-flavored URLs.

    ProviderPolicyRequired optionsSetup guide
    Auth0mcp-auth0-oauth-inboundauth0Domain, clientId, clientSecretConfiguring Auth0
    Amazon Cognitomcp-cognito-oauth-inboundawsRegion, userPoolId, userPoolDomain, client credsCognito
    Clerkmcp-clerk-oauth-inboundfrontendApiUrl, clientId, clientSecretClerk
    Googlemcp-google-oauth-inboundclientId, clientSecretGoogle
    Keycloakmcp-keycloak-oauth-inboundkeycloakBaseUrl, realm, client credsKeycloak
    Logtomcp-logto-oauth-inboundlogtoEndpoint, clientId, clientSecretLogto
    Microsoft Entra IDmcp-entra-oauth-inboundtenantId, clientId, clientSecretMicrosoft Entra
    Oktamcp-okta-oauth-inboundoktaDomain, client credsOkta
    OneLoginmcp-onelogin-oauth-inboundoneLoginSubdomain, client credsOneLogin
    PingOnemcp-ping-oauth-inboundenvironmentId (or customDomain), client credsPingOne
    WorkOSmcp-workos-oauth-inboundclientId, clientSecretWorkOS
    Any other OIDC IdP (Ory Hydra, Authentik, custom…)mcp-oauth-inboundoidc.issuer, oidc.jwksUrl, browserLogin.urlGeneric OIDC

    The upstream side uses a separate policy, mcp-token-exchange-inbound, one per upstream MCP route. The downstream OAuth policy and the upstream token-exchange policy are usually paired on the same route.

    Which wrapper should I pick?

    • Use a first-class wrapper when one exists for your IdP. The wrappers validate provider-specific inputs at boot (Entra rejects multi-tenant aliases like common, Cognito separates the IDP service domain from the hosted UI domain, OneLogin asks for the bare subdomain) so most misconfigurations fail fast with an obvious error instead of a confusing OIDC failure later.
    • Use mcp-oauth-inbound for any OIDC provider that doesn't ship a dedicated wrapper. Ory Hydra, Authentik, ZITADEL, FusionAuth, custom OIDC servers, and PingFederate (as opposed to PingOne) all work this way. You provide the OIDC URLs explicitly.

    Sessions, scopes, and TTLs

    The defaults below affect user-visible behavior and come up often in configuration.

    Browser session

    After the user completes browser login, the gateway sets a zuplo_mcp_session cookie. The cookie persists for 8 hours by default (browserLogin.sessionTtlSeconds). During that window, the user doesn't need to re-authenticate against the IdP for additional OAuth grants — the consent page renders immediately.

    Gateway-issued tokens

    The gateway-issued bearer access token defaults to 15 minutes of lifetime (gateway.accessTokenTtlSeconds = 900). MCP clients refresh as needed.

    Refresh tokens default to roughly 10 years (gateway.refreshTokenTtlSeconds). This is intentional: the gateway is not the system of record for the user's session — the upstream IdP is. Imposing a shorter refresh-token lifetime than the IdP's own session policy forces the user back through browser login when the IdP would still accept a silent renewal. Customers who want a tighter ceiling can override the default in policy options.

    Refresh tokens rotate on every use, and presenting a previously rotated refresh token revokes the entire grant (with a short grace window to handle concurrent refreshes).

    Gateway scope

    There is exactly one downstream OAuth scope today: mcp:tools. The gateway issues every access token with this scope, and the PRM advertises it as the only entry in scopes_supported. Future capability scopes will appear alongside mcp:tools rather than replacing it.

    Upstream OAuth scopes are independent — they're whatever the upstream provider requires, configured per upstream on mcp-token-exchange-inbound.

    Authentication binding

    Every gateway-issued access token is bound to:

    • The canonical resource URI of the MCP route the user authorized for, derived from the request origin and the route path.
    • The operationId of the route, set in routes.oas.json.

    The gateway rejects a token presented at a different route or a different canonical resource. A token issued for /mcp/linear-v1 cannot be reused on /mcp/stripe-v1.

    The canonical resource URI is constructed from the incoming request origin. If you front the gateway with a custom domain or a proxy, the gateway derives its origin from the Host or X-Forwarded-Host header. A misconfigured proxy that strips or overwrites these headers makes the gateway advertise the wrong issuer in AS metadata. See Troubleshooting for symptoms.

    Custom domain caveat

    The gateway's issuer URL — the value that appears as issuer in AS metadata and as the authority of all generated endpoint URLs — is derived from the incoming request's origin. The gateway honors the Host and X-Forwarded-Host headers in that order.

    When you put the gateway behind a custom domain (gateway.example.com), ensure your fronting proxy or CDN forwards the original Host (or sets X-Forwarded-Host) so the issuer in AS metadata matches the URL the MCP client connected to. A mismatch makes OAuth clients reject the gateway's metadata.

    Endpoints reference

    The gateway exposes the following authorization endpoints automatically once an MCP OAuth policy is configured. See the reference for the full URL catalog.

    PathMethodPurpose
    /.well-known/oauth-authorization-serverGETRFC 8414 AS metadata (gateway-wide).
    /.well-known/oauth-authorization-server/{routePath}GETRFC 8414 AS metadata (per route, rebinds issuer).
    /.well-known/oauth-protected-resource/{routePath}GETRFC 9728 PRM (per route).
    /__zuplo/oauth/registerPOSTRFC 7591 Dynamic Client Registration.
    /__zuplo/oauth/authorizeGETGateway-wide authorize endpoint. Requires the resource parameter.
    /__zuplo/oauth/authorize/{routePath}GETPer-route authorize endpoint.
    /__zuplo/oauth/callbackGETBrowser-login callback from the IdP.
    /__zuplo/oauth/setupGET, POSTConsent and multi-upstream connect page.
    /__zuplo/oauth/tokenPOSTToken endpoint. Accepts authorization_code and refresh_token grants.
    /__zuplo/oauth/revokePOSTRFC 7009 revocation.
    /__zuplo/.well-known/oauth-client/{connection}GETOIDC Client ID Metadata Document for the upstream OAuth client (per upstream).
    /__zuplo/auth/connections/{connection}/connectGETStart the upstream OAuth flow.
    /__zuplo/auth/connections/{connection}/callbackGETUpstream OAuth callback.

    The well-known metadata endpoints serve CORS-permissive responses (Access-Control-Allow-Origin: *) because browser-resident MCP clients fetch them cross-origin. The token, register, revoke, callback, setup, and connect endpoints reject ambient credentials.

    Related

    • Per-user OAuth to upstream MCP servers — the upstream side, conceptually: discovery, client registration modes, per-user storage, refresh, and reconsent.
    • Connect a gateway to an upstream OAuth provider — how to attach the mcp-token-exchange-inbound policy.
    • Manual OAuth testing — verify the gateway's OAuth surface end to end with curl and openssl.
    • Identity provider setup guides — Auth0, Cognito, Clerk, Google, Keycloak, Logto, Microsoft Entra, Okta, OneLogin, PingOne, WorkOS, and any other OIDC provider.
    • Reference — full URL catalog, default TTLs, and OAuth metadata extensions.
    Edit this page
    Last modified on September 21, 2026
    Test clientsUpstream OAuth
    On this page
    • The two layers
      • Downstream: gateway as OAuth server
      • Upstream: gateway as OAuth client
    • Standards observed
    • Downstream flow
    • Upstream flow
    • Identity providers
      • Which wrapper should I pick?
    • Sessions, scopes, and TTLs
      • Browser session
      • Gateway-issued tokens
      • Gateway scope
      • Authentication binding
    • Custom domain caveat
    • Endpoints reference
    • Related