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.0 | MCP Requirement | OAuth 2.1 Fix |
|---|---|---|
| PKCE optional | Code interception attacks critical for AI agents | PKCE mandatory with S256 |
| Implicit flow allowed | Token leakage in agent-to-server communication | Implicit flow removed |
| No audience binding | Token confusion between MCP servers | RFC 8707 resource indicators mandatory |
| No discovery standard | Zero-config federation needed for dynamic agents | RFC 8414 + RFC 9728 |
| ROPC flow allowed | Plaintext passwords unacceptable | ROPC removed |
| Manual client registration | Agents need automatic registration | CIMD + 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:
https://auth.example.com/.well-known/oauth-authorization-server/tenant1https://auth.example.com/.well-known/openid-configuration/tenant1https://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.
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:
| Provider | RFC 8707 (Resource Indicators) | RFC 7591 (DCR) | MCP Compliance |
|---|---|---|---|
| PingFederate 12.1+ | Yes | Yes | Fully compliant |
| Keycloak | Custom mapper needed | Yes | Partial |
| Amazon Cognito | Yes | No | Partial |
| Auth0 | Non-standard audience param | Partial | Incompatible |
| Okta | No | Partial | Incompatible |
| Microsoft Entra ID | Proprietary syntax | No | Incompatible |
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
| Aspect | Traditional Web App | MCP |
|---|---|---|
| Actor | Human in browser | AI agent (often unattended) |
| Client discovery | Pre-configured client_id | Dynamic via PRM + CIMD |
| Token audience | Often implicit | Mandatory RFC 8707 binding |
| PKCE | Recommended | Mandatory S256 |
| Scope model | Fixed at registration | Progressive step-up elevation |
| Token passthrough | Common in microservices | Explicitly forbidden |
| Multi-hop | Single client-server | User → 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:
- Implement
/.well-known/oauth-protected-resource(RFC 9728) returning your authorization server URL - Return
WWW-Authenticateheaders on 401 responses with theresource_metadataURL - Validate JWT tokens using JWKS, checking
iss,aud,exp, andscopeclaims - Use the MCP Auth SDK for Python or TypeScript
- For Cloudflare Workers, use
workers-oauth-providerwhich wraps the full MCP OAuth flow
Related Articles
- Agentic AI Authentication: Securing AI Agents in Enterprise Systems
- The API Authorization Hierarchy of Needs: Why You Aren’t Ready for AI Agents Yet
- OAuth 2.1 Security Best Practices: Mandatory PKCE and Token Binding
- Understanding the Authorization Code Flow with PKCE in OAuth 2.0
- OAuth 2.1: What’s Changing and Why It Matters
