The Model Context Protocol (MCP) defines how AI agents connect to external tools and data sources. When an MCP client (like Claude Desktop or a custom AI agent) needs to access a protected MCP server, it uses OAuth 2.1 — not OAuth 2.0 — as the authorization mechanism. This article explains exactly how MCP authentication works, what makes it different from traditional OAuth, and which identity providers actually support it.

Why MCP Uses OAuth 2.1, Not 2.0

OAuth 2.0 has six gaps that MCP cannot tolerate:

Gap in OAuth 2.0MCP RequirementOAuth 2.1 Fix
PKCE optionalCode interception attacks critical for AI agentsPKCE mandatory with S256
Implicit flow allowedToken leakage in agent-to-server communicationImplicit flow removed
No audience bindingToken confusion between MCP serversRFC 8707 resource indicators mandatory
No discovery standardZero-config federation needed for dynamic agentsRFC 8414 + RFC 9728
ROPC flow allowedPlaintext passwords unacceptableROPC removed
Manual client registrationAgents need automatic registrationCIMD + DCR support

The Discovery Trifecta

MCP’s most novel feature is zero-configuration discovery. An MCP client can connect to any compliant MCP server without pre-configured endpoints.

Step 1: Trigger Authentication

The client sends an unauthenticated request to the MCP server and receives:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="mcp:tools:weather"

Step 2: Protected Resource Metadata (RFC 9728)

The client fetches the MCP server’s resource metadata:

GET /.well-known/oauth-protected-resource HTTP/1.1
Host: mcp.example.com
{
  "resource": "https://mcp.example.com",
  "authorization_servers": ["https://auth.example.com"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["mcp:tools:weather", "mcp:tools:calendar:read"]
}

This tells the client which authorization server to use — the MCP server itself is NOT the authorization server.

Step 3: Authorization Server Metadata (RFC 8414)

The client discovers the AS endpoints. For issuer https://auth.example.com/tenant1, clients MUST try these URLs in order:

  1. https://auth.example.com/.well-known/oauth-authorization-server/tenant1
  2. https://auth.example.com/.well-known/openid-configuration/tenant1
  3. https://auth.example.com/tenant1/.well-known/openid-configuration

Client Registration: Three Approaches

MCP clients register with the authorization server using one of three mechanisms (in priority order):

1. Pre-registration — Use an existing client_id if available.

2. Client ID Metadata Documents (CIMD) — The client hosts a JSON document at an HTTPS URL and uses that URL as its client_id:

{
  "client_id": "https://app.example.com/oauth/client-metadata.json",
  "client_name": "My MCP Client",
  "redirect_uris": ["http://127.0.0.1:3000/callback"],
  "grant_types": ["authorization_code"],
  "token_endpoint_auth_method": "none"
}

The AS fetches and validates this document automatically. CIMD replaced Dynamic Client Registration (DCR) as the preferred approach in the November 2025 spec revision.

3. Dynamic Client Registration (RFC 7591) — Fallback if the AS has a registration_endpoint.

Authorization Code Flow with Mandatory PKCE

PKCE is non-negotiable in MCP. Clients MUST use S256 and MUST verify code_challenge_methods_supported in the AS metadata before proceeding. If S256 is not listed, the client MUST refuse to continue.

GHHEToTr&&&&&&&TsecrsccrsPt/slecooet/:apidoddsa1uoeipeeot.atnnre__ue1uhste=ccr=toe_cmhhcrhr_itcaaea.itd_pll=nezy=u:llhdxephrteetoa?etionntmm=t=oggp-pcphleesslosts=_:ted:t:Em/a.e/pw9e/tc/:eMtmeoa/aehc-mp/tlopxplhhd.y.oeo=ezecraSxxa22aalO5mmhw6ppovllsFeetr..:Ecc3Moo0Tmm0J/0go/uacCuaHtlahlo/becaKlc1ikte8nUtR-WmbeutGaJdSasttaw.-jcsMon

The resource parameter (RFC 8707) binds the token to the specific MCP server. This prevents token confusion attacks where a token issued for one MCP server is used against another.

Token Validation on the MCP Server

MCP servers validate tokens as standard JWT bearer tokens:

import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks')
);

const validateToken = async (token) => {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://auth.example.com',
    audience: 'https://mcp.example.com'  // Must match resource parameter
  });
  return {
    userId: payload.sub,
    scopes: payload.scope?.split(' ') || [],
    clientId: payload.client_id
  };
};

Critical rule: MCP servers MUST NOT relay client tokens to downstream services. Token passthrough is explicitly forbidden in the spec.

Step-Up Authorization

MCP supports progressive scope elevation. When a client has a valid token but needs additional permissions, the server returns:

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                         scope="mcp:tools:weather mcp:tools:calendar:write"

The client then initiates a new authorization flow with the required scopes.

IdP Compatibility Matrix

Most major identity providers are NOT fully compliant with MCP’s requirements:

ProviderRFC 8707 (Resource Indicators)RFC 7591 (DCR)MCP Compliance
PingFederate 12.1+YesYesFully compliant
KeycloakCustom mapper neededYesPartial
Amazon CognitoYesNoPartial
Auth0Non-standard audience paramPartialIncompatible
OktaNoPartialIncompatible
Microsoft Entra IDProprietary syntaxNoIncompatible

The primary bottleneck is RFC 8707 (Resource Indicators). Most providers predate this standard and use proprietary audience parameters instead.

MCP vs Traditional OAuth: Key Differences

AspectTraditional Web AppMCP
ActorHuman in browserAI agent (often unattended)
Client discoveryPre-configured client_idDynamic via PRM + CIMD
Token audienceOften implicitMandatory RFC 8707 binding
PKCERecommendedMandatory S256
Scope modelFixed at registrationProgressive step-up elevation
Token passthroughCommon in microservicesExplicitly forbidden
Multi-hopSingle client-serverUser → AI Host → MCP Client → MCP Servers

Security Considerations

Confused Deputy Attack

When an MCP proxy server uses a static client_id with a third-party AS, an attacker can exploit pre-existing consent cookies to obtain authorization codes redirected to their own server. MCP proxy servers MUST implement per-client consent before forwarding to third-party authorization servers.

Sender-Constrained Tokens

The spec recommends DPoP (Demonstrating Proof-of-Possession) or mTLS to prevent token replay. A stolen bearer token alone becomes useless without the client’s private key.

SSRF in Discovery

MCP clients fetch URLs from potentially malicious MCP servers during discovery. Mitigations include enforcing HTTPS, blocking private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and validating redirect targets.

Getting Started

To implement MCP OAuth in your server:

  1. Implement /.well-known/oauth-protected-resource (RFC 9728) returning your authorization server URL
  2. Return WWW-Authenticate headers on 401 responses with the resource_metadata URL
  3. Validate JWT tokens using JWKS, checking iss, aud, exp, and scope claims
  4. Use the MCP Auth SDK for Python or TypeScript
  5. For Cloudflare Workers, use workers-oauth-provider which wraps the full MCP OAuth flow