> ## Documentation Index
> Fetch the complete documentation index at: https://tyk.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Gateway: OAuth 2.1 Authentication

> Configure OAuth 2.1 for MCP proxies in Tyk Gateway: PRM discovery, inbound Bearer token validation, RFC 8693 token exchange, and upstream OAuth.

***

## MCP Auth Model

MCP authorization operates on two distinct planes.

**Inbound authorization** governs how MCP clients (AI agents, LLM frameworks, and applications) authenticate to Tyk Gateway. Tyk validates the credential on every request before it reaches your upstream MCP server. All [authentication methods](https://tyk.io/docs/api-management/client-authentication.md) supported by Tyk apply here: Bearer tokens, API keys, JWT, and mutual TLS.

> [!NOTE]
>
> For OAuth 2.1 compliance, configure the `oauth2` security scheme. It publishes the Protected Resource Metadata discovery document, enforces per-operation scopes, and enables RFC 8693 token exchange, all from a single scheme declaration. See [OAuth 2.0 (External IdP)](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md).

**Upstream authorization** governs how Tyk authenticates to your upstream MCP server when that server requires an OAuth token. Tyk obtains the token from the authorization server using the client credentials flow and injects it into every proxied request. Your upstream receives a properly authorized request without any involvement from the original caller.

The two planes are configured independently: Bearer tokens on the inbound side can be combined with client credentials on the upstream side.

***

## Protected Resource Metadata

**Protected Resource Metadata (PRM)** is the standardized discovery mechanism defined in [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728). It gives OAuth clients a machine-readable document describing a protected resource: which authorization servers can issue tokens for it, and which OAuth scopes it supports.

The MCP specification requires MCP servers that support authorization to expose a PRM document, so that clients can discover the correct authorization server before making their first request. Without it, clients must be pre-configured with authorization server URLs, an approach that becomes fragile as deployments grow and authorization infrastructure changes.

Tyk serves the PRM document natively. When PRM is enabled on an [MCP OAS definition](https://tyk.io/docs/ai-management/mcp-gateway/mcp-proxy-definitions.md), Tyk intercepts GET requests to the well-known path and serves the metadata document. The endpoint is unauthenticated by design because clients need it before they have a token.

### The Discovery Flow

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as MCP Client
    participant T as Tyk Gateway
    participant A as Authorization Server
    participant U as Upstream MCP Server

    C->>T: POST /mcp (no token)
    T-->>C: 401 Unauthorized
    Note over T,C: WWW-Authenticate: Bearer resource_metadata="https://gateway.example.com/weather-mcp/.well-known/oauth-protected-resource"

    C->>T: GET /weather-mcp/.well-known/oauth-protected-resource
    T-->>C: 200 OK · PRM document
    Note over T,C: resource · authorization_servers: [https://auth.example.com] · scopes_supported

    C->>A: GET /.well-known/oauth-authorization-server
    A-->>C: 200 OK · authorization server metadata
    Note over C: Check issuer matches https://auth.example.com

    Note over C,A: Obtain a client ID (Client ID Metadata Document, pre-registration, or Dynamic Client Registration)
    C->>A: Authorization request · PKCE · resource=https://gateway.example.com/weather-mcp/
    A-->>C: Authorization code · iss
    Note over C: Check iss matches the recorded issuer
    C->>A: Token request · code_verifier · resource
    A-->>C: 200 OK · access_token

    C->>T: POST /mcp · Authorization: Bearer access_token
    Note over T: Validate token · apply middleware
    T->>U: POST /mcp (upstream credentials, if configured)
    U-->>T: Response
    T-->>C: Response
```

The `WWW-Authenticate` header Tyk sends on authentication failure is a standard Bearer challenge extended with the `resource_metadata` parameter defined in RFC 9728. Tyk also serves the PRM document at `/.well-known/oauth-protected-resource/{listen-path}`, the fallback location that clients try when the header is missing.

In mirror mode, Tyk passes the upstream's authorization server metadata to clients. A client that checks `issuer` strictly can reject it. Enable the [OAuth broker](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md#oauth-broker) to fix this.

### Configuring PRM

PRM is configured within the `oauth2` security scheme block in the Tyk Vendor Extension. The scheme name (`idpAuth` in the example below) must match the scheme declared with `type: oauth2` in the OAS `components.securitySchemes` section. See [OAuth 2.0 (External IdP)](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md) for the full scheme setup.

```json theme={null}
{
  "x-tyk-api-gateway": {
    "server": {
      "authentication": {
        "enabled": true,
        "securitySchemes": {
          "idpAuth": {
            "enabled": true,
            "protectedResourceMetadata": {
              "enabled": true,
              "resource": "https://gateway.example.com/weather-mcp/",
              "authorizationServers": ["https://auth.example.com"],
              "autoDeriveScopes": true
            }
          }
        }
      }
    }
  }
}
```

The PRM endpoint is served at `{listen-path}/{wellKnownPath}`. With the default path and a listen path of `/weather-mcp/`, the endpoint is available at `/weather-mcp/.well-known/oauth-protected-resource`.

For every PRM field, its default, and the automatic migration from the pre-5.14 location, see [Protected Resource Metadata](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md#protected-resource-metadata).

***

## Scope Enforcement

Scope enforcement checks that the bearer token carries the scopes required by the matched operation or MCP primitive, translating what the authorization server granted into access decisions at the gateway.

Configure it under `scopeCheck` in the `oauth2` scheme block:

```json theme={null}
{
  "x-tyk-api-gateway": {
    "server": {
      "authentication": {
        "securitySchemes": {
          "idpAuth": {
            "enabled": true,
            "scopeCheck": {
              "enabled": true,
              "claimNames": ["scope", "scp"],
              "separator": " ",
              "scopeSource": "union"
            }
          }
        }
      }
    }
  }
}
```

The required scopes for each operation come from the `security:` array in your OAS definition. For MCP primitives, which have no OAS path entry, declare scopes in the Tyk Vendor Extension under `middleware.mcpTools`, `middleware.mcpResources`, or `middleware.mcpPrompts`:

```json theme={null}
{
  "x-tyk-api-gateway": {
    "middleware": {
      "mcpTools": {
        "create-report": {
          "security": [{ "idpAuth": ["tools:write"] }]
        }
      }
    }
  }
}
```

When a request fails scope enforcement, Tyk returns `403 Forbidden` with `WWW-Authenticate: Bearer error="insufficient_scope"`. Scope enforcement runs after JWT signature validation. If the token is invalid, the request is rejected at the JWT auth step before scope check is reached.

For the `scopeCheck` fields, the `scopeSource` modes, and per-operation and per-primitive exemptions, see [Scope enforcement](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md#scope-enforcement).

***

## Upstream OAuth

When the upstream MCP server requires OAuth, Tyk handles token acquisition transparently. It acts as an OAuth client, obtaining a token from the upstream's authorization server using the client credentials flow and attaching it to every proxied request. The original MCP client never needs to supply upstream credentials, and Tyk never forwards the client's own token to the upstream.

Tyk caches each token in Redis until it expires, encrypted with the Gateway `secret`, and shares it across all consumers of the proxy. When the cached token expires, Tyk requests a new one on the next request, so the authorization server isn't called on every MCP request.

> [!NOTE]
>
> Upstream OAuth is available in Tyk Enterprise Edition only.

### Client Credentials Flow

Client credentials is the OAuth flow for machine-to-machine access. No user is involved: Tyk authenticates to the authorization server as itself and receives a token for the upstream. OAuth 2.1 keeps this grant for confidential clients, which can hold a client secret securely, and Tyk is one.

Because the token represents Tyk rather than the end user, the upstream MCP server sees every request as coming from Tyk. If the upstream needs the user's identity, use [token exchange](#token-exchange) instead.

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as MCP Client
    participant T as Tyk Gateway
    participant A as Authorization Server
    participant U as Upstream MCP Server

    C->>T: POST /mcp · Authorization: Bearer inbound-token
    Note over T: Validate inbound token · check token cache

    alt Token not cached or expired
        T->>A: POST /oauth/token
        Note over T,A: grant_type: client_credentials · scope · client credentials (Basic auth header or request body)
        A-->>T: 200 OK · access_token · expires_in: 3600
        Note over T: Cache upstream token
    else Token cached
        Note over T: Use cached upstream token
    end

    T->>U: POST /mcp · Authorization: Bearer upstream-token
    U-->>T: Response
    T-->>C: Response
    Note over C,U: Request processed upstream
```

### Configuring Upstream OAuth

Upstream OAuth is configured in the `upstream.authentication` section of the API definition:

```json theme={null}
{
  "x-tyk-api-gateway": {
    "upstream": {
      "url": "https://weather-mcp.example.com",
      "authentication": {
        "enabled": true,
        "oauth": {
          "enabled": true,
          "allowedAuthorizeTypes": ["clientCredentials"],
          "clientCredentials": {
            "clientId": "tyk-gateway-client",
            "clientSecret": "your-client-secret",
            "tokenUrl": "https://auth.example.com/oauth/token",
            "scopes": ["mcp:read", "mcp:write"]
          }
        }
      }
    }
  }
}
```

| Field | Required | Description |
| - | - | - |
| `clientId` | Yes | The OAuth client ID issued by the authorization server for this gateway instance. |
| `clientSecret` | Yes | The client secret associated with the client ID. |
| `tokenUrl` | Yes | The token endpoint of the upstream's authorization server. |
| `scopes` | No | The scopes to request when obtaining the token. The authorization server grants only the scopes it recognises and the client is permitted. |
| `extraMetadata` | No | Keys to extract from the token response and pass to the upstream as additional context. |

***

## Token Exchange

The [MCP authorization specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations#access-token-privilege-restriction) requires that an MCP server **MUST NOT** pass through the token it received from the MCP client when making requests to upstream APIs. The upstream token must be a separate token issued by the upstream authorization server.

**Token exchange** ([RFC 8693](https://www.rfc-editor.org/rfc/rfc8693)) is how Tyk satisfies this requirement while preserving the caller's identity. Tyk presents the validated inbound token to an authorization server and receives a backend-scoped token in return. The inbound agent token never reaches the upstream.

This matters for MCP because:

* **Spec compliance**: The MCP spec explicitly forbids passing through the inbound token. Token exchange satisfies this requirement while maintaining a traceable delegation chain.
* **Audience compliance**: MCP servers must validate that tokens were issued specifically for them as the intended audience ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)). Token exchange produces a token audienced to the upstream service without requiring clients to request multiple tokens.
* **Separation of trust**: The AI agent's credential stays scoped to the gateway trust domain; the upstream receives a narrowly-scoped credential appropriate to its own.

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as MCP Client
    participant T as Tyk Gateway
    participant I as Authorization Server
    participant U as Upstream MCP Server

    C->>T: POST /mcp · Authorization: Bearer agent-token
    Note over T: Validate inbound token · enforce scopes

    T->>I: POST /token (RFC 8693 exchange)
    Note over T,I: subject_token=agent-token · audience=upstream-service
    I-->>T: 200 OK · access_token (upstream-scoped)

    T->>U: POST /mcp · Authorization: Bearer upstream-token
    U-->>T: Response
    T-->>C: Response
    Note over C,U: Inbound token never forwarded to upstream
```

Token exchange is Enterprise Edition only and is configured within the `oauth2` security scheme. For full configuration details, including provider setup, caching, and per-primitive overrides, see [Token exchange](https://tyk.io/docs/api-management/authentication/token-exchange.md).

> [!NOTE]
>
> Do not configure `upstream.authentication.oauth` alongside token exchange. They serve the same purpose (authenticating Tyk to the upstream), and configuring both produces a conflict. Use token exchange when you want to propagate a delegated credential derived from the inbound token. Use `upstream.authentication.oauth` when Tyk should authenticate with its own static client credentials independent of who the original caller was.

***

## Composing Features

Each feature on this page is independent. The example below uses all of them, but you can enable any subset depending on your requirements.

**JWT authentication** validates the inbound token's signature. In Tyk 5.14.0 this is required alongside the `oauth2` scheme because the `oauth2` scheme reads token claims but does not verify the signature itself. It is the baseline for everything else.

**PRM** enables dynamic discovery: clients that arrive without a token learn where to obtain one. Enable it if your MCP clients are OAuth 2.1-compliant and should discover the authorization server automatically. Omit it if your clients are pre-configured with the authorization server URL.

**Scope enforcement** (`scopeCheck`) checks that the token carries the scopes required by the matched operation or MCP primitive. Use it when the IdP owns access decisions: the authorization server grants specific scopes and Tyk enforces them at the gateway. If you prefer Tyk's policy engine to control primitive access (a platform-owned model where keys are issued with policy-level permissions), omit `scopeCheck` and rely on policies instead. The two approaches are mutually exclusive per primitive: using both creates conflicting ownership of access decisions.

**Token exchange** replaces the inbound agent token with an upstream-scoped token before forwarding. The MCP specification requires that the upstream never receives the original token. Token exchange is how you satisfy that requirement while preserving the delegation chain. If you are using [Upstream OAuth](#upstream-oauth) with static client credentials instead, token exchange can be omitted.

**The OAuth broker** applies to mirror-mode MCP proxies and cannot be combined with upstream authentication. See [OAuth Broker](https://tyk.io/docs/api-management/authentication/oauth2-authentication.md#oauth-broker).

**PRM and scope enforcement are particularly complementary.** PRM advertises which scopes clients need to request; `scopeCheck` enforces at runtime that the presented token actually carries them. When `autoDeriveScopes` is enabled, both are driven by the same `security:` declarations in your OAS definition, so the scopes a client is told to request and the scopes Tyk enforces are derived from the same source and can't drift out of sync. Enabling PRM without `scopeCheck` means scopes are advertised but not enforced at the gateway. Enabling `scopeCheck` without PRM means enforcement works, but clients need to know the required scopes upfront rather than discovering them dynamically.

Common configurations:

| Goal | Features to enable |
| - | - |
| Validate tokens; use Tyk policies for primitive access | JWT auth |
| Add client-side discovery | JWT auth + PRM |
| Add IdP-owned scope enforcement | JWT auth + PRM + scopeCheck |
| Full MCP spec compliance with delegated upstream identity | JWT auth + PRM + scopeCheck + token exchange |

***

## The Complete OAuth 2.1 Flow

The end-to-end flow has two phases. The authorization server appears in both: first when the MCP client obtains its agent token, then when Tyk exchanges it for an upstream-scoped token.

**Phase 1: Discovery**

1. The MCP client makes a request to the MCP endpoint without a token.
2. Tyk returns `401 Unauthorized` with a `WWW-Authenticate` header pointing to the PRM well-known URL.
3. The client fetches the PRM document and learns which authorization server to use and which scopes are supported.
4. The client authenticates to the authorization server (authorization code flow, device flow, etc.) and receives an agent-scoped access token.

**Phase 2: Authenticated Request**

5. The client sends the request with `Authorization: Bearer <agent-token>`.
6. Tyk validates the token signature (JWT auth) and enforces scopes (`oauth2` scheme).
7. Tyk POSTs an RFC 8693 token exchange to the authorization server, presenting the agent token as `subject_token` and requesting a token audienced to the upstream MCP server.
8. The authorization server returns an upstream-scoped access token.
9. Tyk replaces the `Authorization` header with the exchanged token and forwards the request to the upstream MCP server.
10. The upstream responds; Tyk returns the response to the client.

The agent's original token never reaches the upstream MCP server. The MCP client and the upstream each receive a token issued specifically for their trust boundary.

***

## A Complete Configuration Example

The following configuration for a weather MCP proxy enables all four features: PRM discovery, JWT signature validation, scope enforcement, and RFC 8693 token exchange:

```json expandable theme={null}
{
  "openapi": "3.0.3",
  "info": { "title": "Weather MCP proxy", "version": "2025-11-25" },
  "components": {
    "securitySchemes": {
      "jwtAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "idpAuth": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.example.com/authorize",
            "tokenUrl": "https://auth.example.com/token",
            "scopes": {
              "tools:read": "Read access to MCP tools",
              "tools:write": "Write access to MCP tools"
            }
          }
        }
      }
    }
  },
  "security": [
    { "jwtAuth": [], "idpAuth": ["tools:read"] }
  ],
  "paths": {
    "/mcp": {
      "post": { "operationId": "mcpTransportPost", "responses": { "200": { "description": "JSON-RPC response" } } },
      "get":  { "operationId": "mcpSSEGet",         "responses": { "200": { "description": "SSE stream" } } }
    }
  },
  "x-tyk-api-gateway": {
    "info": {
      "name": "Weather MCP proxy",
      "state": { "active": true }
    },
    "server": {
      "listenPath": { "value": "/weather-mcp/", "strip": true },
      "authentication": {
        "enabled": true,
        "securitySchemes": {
          "jwtAuth": {
            "enabled": true,
            "signingMethod": "rsa",
            "jwksURIs": [{ "url": "https://auth.example.com/.well-known/jwks.json" }],
            "identityBaseField": "sub"
          },
          "idpAuth": {
            "enabled": true,
            "scopeCheck": {
              "enabled": true,
              "claimNames": ["scope", "scp"],
              "separator": " ",
              "scopeSource": "union"
            },
            "protectedResourceMetadata": {
              "enabled": true,
              "resource": "https://gateway.example.com/weather-mcp/",
              "authorizationServers": ["https://auth.example.com"],
              "autoDeriveScopes": true
            },
            "tokenExchange": {
              "enabled": true,
              "providers": [
                {
                  "name": "idp-prod",
                  "issuers": ["https://auth.example.com"],
                  "tokenEndpoint": "https://auth.example.com/token",
                  "clientAuth": {
                    "method": "client_secret_basic",
                    "clientId": "tyk-gateway",
                    "clientSecret": "env://EXCHANGE_CLIENT_SECRET"
                  },
                  "defaultTarget": {
                    "audience": "https://weather-mcp.example.com",
                    "scopes": ["mcp:read", "mcp:write"]
                  }
                }
              ]
            }
          }
        }
      }
    },
    "upstream": {
      "url": "https://weather-mcp.example.com"
    }
  }
}
```

In this configuration:

* MCP clients that arrive without a token receive a `401` with a `WWW-Authenticate` header pointing to the PRM document.
* Clients that follow the discovery flow obtain a token from `https://auth.example.com` using the `authorizationCode` flow and present it as a Bearer token.
* Tyk validates the token signature via the `jwtAuth` scheme (JWKS endpoint), then `scopeCheck` enforces that the token carries `tools:read` before the request proceeds.
* Tyk exchanges the validated agent token for an upstream-scoped token audienced to `https://weather-mcp.example.com`. The original agent token never reaches the upstream MCP server.

> [!NOTE]
>
> In Tyk 5.14.0, the `oauth2` scheme does not validate JWT signatures itself. The `jwtAuth` scheme in this example handles signature validation. Configure JWT authentication on the API alongside the `oauth2` scheme so that inbound tokens are verified before scope enforcement runs.

For the static client credentials variant (where Tyk authenticates to the upstream with its own credentials independent of the inbound token), see [Upstream OAuth](#upstream-oauth).

***

## Per-Primitive Configuration

The complete example above configures each feature at the API level, applying uniformly to all primitives. Both scope enforcement and token exchange support per-primitive overrides when individual tools need different access requirements or different upstream credentials.

### Multiple Exchange Providers

The `providers` array accepts more than one entry. Tyk selects a provider for each request by matching the inbound token's `iss` claim against each provider's `issuers` list. Use this when an MCP proxy accepts tokens from more than one authorization server, for example your own IdP and a partner's.

```json expandable theme={null}
"tokenExchange": {
  "enabled": true,
  "providers": [
    {
      "name": "idp-prod",
      "issuers": ["https://auth.example.com"],
      "tokenEndpoint": "https://auth.example.com/token",
      "clientAuth": {
        "method": "client_secret_basic",
        "clientId": "tyk-gateway",
        "clientSecret": "env://EXCHANGE_CLIENT_SECRET"
      },
      "defaultTarget": {
        "audience": "https://weather-mcp.example.com",
        "scopes": ["mcp:read", "mcp:write"]
      }
    },
    {
      "name": "partner-idp",
      "issuers": ["https://auth.partner.example"],
      "tokenEndpoint": "https://auth.partner.example/token",
      "clientAuth": {
        "method": "client_secret_post",
        "clientId": "tyk-gateway-partner",
        "clientSecret": "env://PARTNER_EXCHANGE_SECRET"
      },
      "defaultTarget": {
        "audience": "https://weather-mcp.example.com",
        "scopes": ["mcp:read"]
      }
    }
  ]
}
```

For the provider fields and multi-tenant issuer matching, see [Provider fields](https://tyk.io/docs/api-management/authentication/token-exchange.md#provider-fields).

### Per-Primitive Scope and Exchange Overrides

The `defaultTarget` in each provider applies uniformly to every primitive. When individual tools need a different audience, a narrower set of scopes, or stricter scope enforcement than the API-level default, configure overrides directly on the primitive in the `middleware.mcpTools` map:

```json theme={null}
"middleware": {
  "mcpTools": {
    "get-forecast": {
      "security": [{ "idpAuth": ["tools:read"] }],
      "scopeCheck": { "enabled": true }
    },
    "delete-station": {
      "security": [{ "idpAuth": ["tools:write", "admin:stations"] }],
      "scopeCheck": { "enabled": true },
      "exchange": {
        "enabled": true,
        "audience": "https://station-admin.example.com",
        "scopes": ["admin:stations"]
      }
    }
  }
}
```

* `get-forecast` requires `tools:read`. Scope enforcement rejects tokens that don't carry that claim. The exchange uses the provider's `defaultTarget` audience and scopes unchanged.
* `delete-station` requires both `tools:write` and `admin:stations`. The `exchange` override requests a token audienced specifically to `https://station-admin.example.com`, a narrower credential than the standard weather MCP token, carrying only the `admin:stations` scope.

For the full `scopeCheck` and `exchange` field reference, see [MCP middleware](https://tyk.io/docs/ai-management/mcp-gateway/mcp-middleware.md).
