2026-02-11

A Developer's Guide to MCP Authentication

The standards, protocols, and patterns behind secure AI tool access — OAuth 2.1, PKCE, CIMD, and more.

A Developer's Guide to MCP Authentication

Written by

Joey Orlando

Every MCP Connection Starts with a 401

You've just installed an MCP server. Your AI assistant connects, and immediately gets rejected — a 401 Unauthorized response with a cryptic WWW-Authenticate header. What happens next involves at least three RFCs, a discovery protocol, and a client registration step your user never sees.

MCP (Model Context Protocol) standardized on OAuth 2.1 for authentication. If you're building MCP clients, servers, or gateways, understanding how these pieces fit together saves hours of debugging. This post breaks down the full authentication landscape — the standards involved, how discovery works, how clients register, and where the sharp edges are.

This is Part 1 of a two-part series on MCP authentication. Part 2 covers building enterprise MCP servers with JWKS and identity providers.

Why MCP Chose OAuth 2.1

The MCP specification needed an auth framework that works for a specific class of applications: desktop tools, CLI agents, and browser-based chat interfaces connecting to tool servers. These are all public clients — they can't securely store client secrets.

OAuth 2.1 was the natural fit. It mandates two things that matter for MCP:

  1. Authorization Code flow only — no implicit grant, no resource owner password credentials
  2. PKCE (Proof Key for Code Exchange) — protects the authorization code exchange without requiring a client secret

This combination gives MCP clients a secure way to authenticate without embedding secrets in desktop apps or CLI tools.

The Standards Stack

MCP auth relies on a stack of interrelated standards. Here's the full picture:

  • OAuth 2.1 — Authorization framework. The core auth protocol for MCP.
  • PKCE (RFC 7636) — Proof Key for Code Exchange. Protects code exchange for public clients. Required by the MCP spec.
  • RFC 9728 — Protected Resource Metadata. Links a resource server to its authorization server.
  • RFC 8414 — Authorization Server Metadata. Endpoint discovery for authorize, token, register, and JWKS URLs.
  • RFC 7591 — Dynamic Client Registration (DCR). Allows clients to register at runtime.
  • CIMD — Client ID Metadata Documents. URL-based client identity. The MCP default since November 2025.
  • RFC 8628 — Device Authorization Grant. Auth for headless clients that can't open a browser.
  • RFC 8707 — Resource Indicators. Binds tokens to a specific audience/resource server.
  • MCP Authorization Spec — The MCP specification that ties all of the above together.

Don't worry if this looks overwhelming — each piece has a specific job, and they chain together in a predictable sequence.

How MCP Auth Works: Step by Step

1. The Initial 401

When an MCP client first connects to a server, the server responds with:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://server.example/.well-known/oauth-protected-resource"

This isn't an error — it's the handshake that starts the auth flow. The resource_metadata URL tells the client where to find the server's auth configuration.

2. Discovery (RFC 9728 then RFC 8414)

The client follows a two-step discovery chain:

Step 1 — Protected Resource Metadata (RFC 9728): The client fetches the resource metadata URL from the 401 response:

{
  "resource": "https://server.example/v1/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["mcp:tools", "mcp:resources"]
}

This answers: "who is the authorization server for this resource?"

Step 2 — Authorization Server Metadata (RFC 8414): The client fetches /.well-known/oauth-authorization-server from the authorization server:

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "registration_endpoint": "https://auth.example.com/register",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"]
}

Now the client knows every endpoint it needs — no hardcoded URLs required.

3. Client Registration

The client needs a client_id before it can authorize. Two approaches:

DCR (Dynamic Client Registration, RFC 7591). The traditional approach. The client POSTs its metadata to the registration endpoint:

{
  "client_name": "My MCP Client",
  "redirect_uris": ["http://127.0.0.1:34567/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}

The server responds with a client_id. This works well, but creates "client sprawl" at scale — every client registers separately with every server.

CIMD (Client ID Metadata Documents). The MCP default since November 2025. Instead of registering, the client uses an HTTPS URL as its client_id:

client_id=https://cursor.com/.well-known/oauth-client/mcp

The authorization server fetches this URL and gets the client's metadata:

{
  "client_id": "https://cursor.com/.well-known/oauth-client/mcp",
  "client_name": "Cursor",
  "redirect_uris": ["https://cursor.com/mcp/callback"],
  "grant_types": ["authorization_code"],
  "token_endpoint_auth_method": "none"
}

No registration endpoint needed. The client's identity is its URL. This is decentralized — the client controls its own metadata, and any server can verify it by fetching the document.

4. Authorization + PKCE

With a client_id in hand, the standard OAuth Authorization Code flow begins — with PKCE:

  1. Client generates a random code_verifier (43-128 characters)
  2. Hashes it to create code_challenge = BASE64URL(SHA256(code_verifier))
  3. Opens the browser to the authorization endpoint with the challenge
  4. User logs in and grants consent
  5. Server redirects back with an authorization code
  6. Client exchanges the code + original code_verifier for tokens
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=abc123
&redirect_uri=http://127.0.0.1:34567/callback
&client_id=https://cursor.com/.well-known/oauth-client/mcp
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The server verifies SHA256(code_verifier) == code_challenge from the original authorization request. This prevents authorization code interception — even if someone steals the code, they can't exchange it without the verifier.

The PKCE reality check: The MCP spec requires PKCE, but not all providers support it. GitHub's OAuth implementation, for example, doesn't support PKCE at all. Gateways like Archestra handle both cases — enforcing PKCE when the provider supports it, gracefully degrading when it doesn't.

5. Token Usage

The client receives access and refresh tokens:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dhl3kJo9sE..."
}

All subsequent MCP requests include the access token:

POST /v1/mcp
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json

{"jsonrpc": "2.0", "method": "tools/list", "id": 1}

Device Flow: Auth for Headless Clients

Not every MCP client can open a browser. CLI tools, CI/CD pipelines, and server-side agents need an alternative. The Device Authorization Grant (RFC 8628) handles this:

  1. Client requests a device code from the authorization server
  2. Server returns a user code and a verification URL
  3. Client displays: "Go to https://auth.example.com/device and enter code: ABCD-1234"
  4. User opens the URL on any device, enters the code, and authorizes
  5. Client polls the token endpoint until authorization completes

This is the same flow you use when logging into a streaming service on a smart TV. It works anywhere — terminals, headless servers, embedded devices.

Resource Indicators: Audience-Bound Tokens

One nuance that trips up many implementations: the MCP spec recommends using Resource Indicators (RFC 8707) to bind tokens to specific resource servers.

When requesting a token, the client includes a resource parameter:

POST /token
...&resource=https://server.example/v1/mcp

This tells the authorization server to scope the token to that specific resource. The resulting token (typically a JWT) includes an aud (audience) claim matching the resource URL.

Why this matters: when one authorization server protects multiple MCP servers, audience binding prevents a token issued for Server A from working on Server B.

Client Adoption: Where Things Stand

MCP auth is still maturing. Here's where major clients stand as of early 2026:

  • Claude Desktop — OAuth 2.1 + PKCE, DCR registration. Full spec compliance.
  • Claude Code — OAuth 2.1 + PKCE, DCR registration. Full spec compliance.
  • Cursor — OAuth 2.1, DCR registration. PKCE support varies.
  • Windsurf — OAuth 2.1, DCR registration. Basic OAuth support.
  • Open WebUI — OAuth 2.1 + PKCE, DCR registration. Full spec compliance.
  • VS Code — OAuth 2.1 + PKCE, CIMD registration. First major client to ship CIMD.

The MCP spec adopted CIMD as the default registration method in the 2025-11-25 revision. VS Code has shipped CIMD support; other clients are expected to follow. DCR remains important for backwards compatibility and enterprise deployments where admins want to control client registration.

Common Pitfalls

A few sharp edges from real-world MCP auth implementations:

PKCE method confusion. The MCP spec requires S256 (SHA-256). Some implementations default to the plain method, which provides no security benefit. Always check code_challenge_methods_supported in the server metadata and use S256.

Redirect URI matching. MCP clients typically use loopback addresses (http://127.0.0.1:{port}/callback) with dynamic ports. The authorization server must support flexible port matching for loopback redirect URIs, as specified in RFC 8252.

Discovery endpoint paths. RFC 9728 specifies path-aware resource metadata: /.well-known/oauth-protected-resource/v1/mcp (with the path suffix), not just /.well-known/oauth-protected-resource. Getting this wrong means clients can't discover the auth server.

Token type confusion. MCP servers can issue either opaque tokens (random strings stored server-side) or JWTs (self-contained, verifiable with public keys). Your validation strategy differs significantly between the two. If you're building a gateway, you need to handle both.

How Archestra Handles This

If you're looking for a concrete implementation of the full auth stack described above, Archestra's MCP Gateway implements the complete flow.

Gateway-level authentication. Archestra acts as both the resource server and the authorization server. MCP clients like Claude Desktop, Claude Code, Cursor, and Open WebUI authenticate automatically using the standard OAuth 2.1 flow — including discovery (RFC 9728 + RFC 8414), client registration (both DCR and CIMD), and Authorization Code + PKCE. For direct API integrations, the gateway also supports static Bearer tokens.

Upstream credential management. The gateway separates client-to-gateway auth (Token A) from gateway-to-upstream-server auth (Token B). Clients never handle upstream credentials directly. Archestra resolves them at runtime — supporting static API keys, OAuth tokens with automatic refresh, and per-user credential resolution for multi-tenant setups where each developer uses their own credentials.

External IdP JWKS. For enterprises with existing identity providers, Archestra can validate JWTs from external IdPs (Okta, Auth0, Keycloak, Entra ID) and propagate them to upstream MCP servers. The gateway validates the JWT signature via JWKS, matches the caller to an Archestra user, enforces team-based access control, and forwards the original JWT to the upstream server — enabling end-to-end identity verification without any Archestra-specific integration on the server side.

PKCE everywhere (mostly). Archestra enforces PKCE when the upstream provider supports it and gracefully degrades when it doesn't — important for providers like GitHub that still lack PKCE support.

For a deeper look at the gateway's auth architecture, see the Archestra Authentication docs.

In Part 2 of this series, we go deeper into one specific pattern: building MCP servers that validate JWTs from enterprise identity providers using JWKS.