Module 09

The MCP Authorization Spec (OAuth 2.1 Foundations)

Building Systems Model Context Protocol (MCP)

Module 9 — The MCP Authorization Spec (OAuth 2.1 Foundations)

Phase 3 · Security, Auth & Entitlements — the heart of the phase. This is the module that most directly serves your goal (“design MCP architecture with entitlement and everything”). By the end you should be able to trace every byte of the authorization flow from an anonymous call to an authorized one, name the RFC behind each step, and explain the 2025-11-25 additions. Read it twice; re-read until the discovery dance is automatic.

Baseline: spec 2025-11-25. The 2026-07-28 release candidate hardens authorization further and aligns more closely with OAuth/OIDC deployments — flagged where relevant.

By the end you can:

  1. Name the OAuth roles and map them onto MCP precisely.
  2. Trace the discovery dance: 401 → Protected Resource Metadata → Authorization Server → token → authorized call.
  3. Explain audience binding, PKCE, and why they defeat specific Module-8 threats.
  4. Explain the 2025-11-25 additions: CIMD, OIDC discovery, authorization extensions, enterprise client management.

1. Scope: where authorization applies

MCP authorization is defined for HTTP-based transports (Streamable HTTP). It is OPTIONAL in the spec, but HTTP servers that need protection SHOULD conform to it. stdio servers do not use OAuth — their trust is OS/process-level (Module 3): the host spawned the process and controls its environment. So everything below assumes a remote server reached over Streamable HTTP — exactly the network trust boundary you drew in Module 8.

Authorization is a transport-level concern: it governs whether the client may talk to the server at all. It establishes identity (authentication). What that identity is allowed to doentitlements — is layered on top and is Modules 10–12. Don’t conflate the two; this module gets you a trustworthy, audience-bound token, not a policy decision.


2. The roles — map OAuth onto MCP exactly

Memorize these four; mislabeling them is the most common expert-level error:

OAuth roleWho in MCPResponsibility
Resource Server (RS)The MCP serverHosts protected tools/resources. Validates tokens. Does not issue them.
ClientThe MCP client (in the host)Obtains a token and calls the RS on the user’s behalf. An OAuth 2.1 public client (typically).
Resource OwnerThe userGrants consent.
Authorization Server (AS)A separate service — usually your IdP (Entra ID, Okta, Auth0, Keycloak, …)Authenticates the user, gets consent, issues access tokens.

The pivotal design decision of MCP’s auth model: the MCP server is a Resource Server, not an Authorization Server. It consumes and validates tokens; it delegates identity to a real AS. This separation is what lets enterprises plug MCP into existing IdPs instead of every server reinventing login. (Servers may embed or co-locate an AS, but the clean, recommended design keeps them separate.)

   ┌────────┐   1. call (no token) → 401 + WWW-Authenticate    ┌──────────────┐
   │  MCP   │ ─────────────────────────────────────────────▶  │   MCP server │
   │ client │ ◀───────────────────────────────────────────    │  (Resource   │
   └───┬────┘                                                  │   Server)    │
       │  2. fetch Protected Resource Metadata (RFC 9728)      └──────┬───────┘
       │     → learn which AS to use                                  │ validates
       │                                                              │ tokens
       │  3-4. discover AS metadata (RFC 8414/OIDC), run             ▼
       │        OAuth 2.1 Auth Code + PKCE                     ┌──────────────┐
       └────────────────────────────────────────────────────▶│  Authorization│
                  5. access token (audience = MCP server)     │  Server (IdP) │
       ◀──────────────────────────────────────────────────── └──────────────┘
   6. call MCP server WITH Bearer token → server validates sig+exp+AUDIENCE → allow

3. The discovery dance (memorize this exact sequence)

This is the spine of MCP auth. Every step has an RFC.

Step 1 — Anonymous call → 401 with a pointer. The client calls the MCP server with no token. The server responds 401 Unauthorized and includes a WWW-Authenticate header carrying a resource_metadata URL — per RFC 9728 §5.1. (Alternatively the client can fetch the well-known URI directly.) The server is saying: “You need a token; here’s where to learn how.”

Step 2 — Fetch Protected Resource Metadata (RFC 9728). The MCP server MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728). The client fetches that JSON document (served at a .well-known/oauth-protected-resource URI). It MUST contain an authorization_servers field listing at least one AS. Now the client knows which AS issues tokens for this resource. This is the indirection that makes MCP IdP-agnostic — the server names its AS rather than being one.

Step 3 — Discover the Authorization Server (RFC 8414 / OIDC). The client fetches the AS’s metadata: OAuth 2.0 Authorization Server Metadata (RFC 8414), and — new in 2025-11-25OpenID Connect Discovery 1.0 is also supported. This yields the AS’s authorization_endpoint, token_endpoint, supported scopes, PKCE support, etc. The client MUST verify the AS advertises PKCE support before proceeding.

Step 4 — Run OAuth 2.1 Authorization Code + PKCE. The client runs the Authorization Code grant with PKCE (Proof Key for Code Exchange). PKCE is mandatory in 2025-11-25. Flow in brief: client generates a code_verifier + code_challenge, sends the user to the authorization_endpoint (user authenticates + consents at the IdP), gets an authorization code back, then exchanges code + code_verifier at the token_endpoint for an access token. PKCE binds the code to the client that started the flow, defeating authorization-code interception (Module 8 T5).

Step 5 — Token with the right audience. The issued access token’s audience must be the MCP server (the client signals the target resource using Resource Indicators, RFC 8707). This is the single most important security property: a token minted for this server and no other.

Step 6 — Authorized call + validation. The client retries the call with Authorization: Bearer <token>. The MCP server (RS) validates: signature/issuer, expiry, scopes, and — critically — audience. It rejects tokens not minted for it. This is what defeats token passthrough / confused deputy (Module 8 T2): even a valid token for a different resource is refused.

The whole dance is discovery-driven: no hardcoded endpoints. The server advertises its auth requirements via metadata; the client follows the chain. That’s what enables dynamic, interoperable client↔server auth across vendors.


4. Audience binding & token passthrough (why §3 step 6 matters)

Internalize this, because it’s the most-tested security concept and central to your architecture goal:

  • A bearer token carries an audience (aud) = the resource it’s for.
  • The RS must check aud matches itself. If a client (or attacker) presents a token minted for Service X, the MCP server must reject it — even if the signature is valid.
  • The MCP server must never take an inbound token and replay it to an upstream API (the “passthrough” anti-pattern). To call upstream on the user’s behalf, it performs Token Exchange (RFC 8693) to obtain a new token whose audience is the upstream and whose scope is minimized. (Detailed in Module 12.)

Audience binding + no-passthrough = the structural defense against the confused-deputy problem. Most MCP auth incidents trace back to violating one of these.


5. Client identity & registration — the 2025-11-25 shift

For the AS to issue a token, it must know which client is asking. How clients register has evolved:

  • Historically: Dynamic Client Registration (DCR, RFC 7591) let clients self-register at the AS at runtime, and/or static pre-registration. DCR is operationally awkward at scale (lots of ephemeral client records to manage).
  • 2025-11-25 default — Client ID Metadata Documents (CIMD). The client identifies itself with a URL (e.g. https://app.example.com/client.json). When the OAuth flow starts, the AS fetches that JSON to learn the client’s metadata (redirect URIs, name, etc.). The URL is the client id. This removes the need for pre-registration or per-AS DCR for many cases and makes client identity portable and inspectable. Expect AS-side controls over which CIMD URLs are accepted in enterprise settings.

This pairs with enterprise / client-registration management improvements in 2025-11-25: organizations get more control over which clients may connect — important when you (the architect) must govern a fleet of agents and apps.


6. Authorization extensions & what 2026-07-28 adds

  • Authorization extensions (2025-11-25) — official hooks that let environments insert custom control over the authorization process (e.g. step-up auth, custom claims, additional checks) without forking the spec. This is the seam where your enterprise entitlement requirements attach to the standard flow.
  • 2026-07-28 (release candidate) — the largest revision since launch, with authorization hardening that aligns more tightly with mainstream OAuth/OIDC deployments. Track it; the concepts here (RS/AS separation, discovery, audience binding, PKCE) are stable, but specific requirements tighten.

7. URL-mode elicitation ↔ auth (callback to Module 5)

Recall URL-mode elicitation (SEP-1036): a server can hand the user a URL to complete a sensitive flow in the browser. This is the clean UX vehicle for the user-authentication leg of the OAuth flow — the user authenticates and consents at the IdP in a real browser, not inside the chat, and the credential never touches the model context. Module 5 and Module 9 connect here: elicitation is how the human step of the dance is presented.


8. Milestone exercise

  1. Trace the dance on paper, end to end, for a remote reports MCP server behind Okta (or Keycloak). For each of the six steps write: the message, the direction, and the RFC it implements. Label where PKCE, audience, and authorization_servers appear.
  2. Audience drill. A client presents a token with aud: "https://other-service" to your MCP server. What must the server do, and which Module-8 threat does that prevent?
  3. Passthrough drill. Your server needs to call the GitHub API for the user. Why must it not forward the inbound token, and what does it do instead (name the RFC)?
  4. CIMD drill. Explain how a client identifies itself via a Client ID Metadata Document and what the AS does with the URL. Contrast with DCR.
  5. Build (capstone of this module). Put your Module 6 server behind Streamable HTTP and a real AS (Keycloak is free and standards-complete; a hosted IdP works too). Implement RFC 9728 metadata + token validation (audience!). Make the full flow succeed, then confirm a wrong-audience token is rejected.

Self-check:

  • Step 1 is a 401 carrying WWW-Authenticate with resource_metadata; step 2 fetches RFC 9728 metadata containing authorization_servers.
  • You used RFC 8414 / OIDC discovery for the AS and verified PKCE support.
  • Your token’s audience is the MCP server (RFC 8707), and the server validates audience on every call.
  • Passthrough drill: no token forwarding; use token exchange (RFC 8693).
  • CIMD: client id is a URL; the AS fetches the JSON to learn client metadata.

When the wrong-audience token is rejected by your server, you’ve operationalized the single most important MCP security property. Next: Module 10 — AuthN vs AuthZ vs Entitlements, where identity becomes policy.


Quick reference — Module 9 glossary

  • RS / AS — MCP server = Resource Server (validates tokens); separate Authorization Server = IdP (issues tokens). Client = OAuth 2.1 public client; user = resource owner.
  • RFC 9728 — Protected Resource Metadata; the server advertises its authorization_servers. Server MUST implement it.
  • RFC 8414 / OIDC Discovery — Authorization Server metadata (endpoints, PKCE support).
  • PKCE — mandatory in 2025-11-25; binds the auth code to the initiating client.
  • Audience binding (RFC 8707) — token minted for the MCP server; server validates aud and rejects others → defeats token passthrough/confused deputy.
  • Token exchange (RFC 8693) — get a new, correctly-audienced token for upstream calls; never forward the inbound token.
  • CIMD (2025-11-25) — client id is a URL; AS fetches the JSON for client metadata; default over DCR (RFC 7591).
  • Authorization extensions (2025-11-25) — official hooks for custom org control; 2026-07-28 hardens auth further.