Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix B: OAuth 2.0 and 2.1 Quick Reference

This page follows the MCP authorization specification in revision 2026-07-28. Chapter 19 builds each step below on the tickets server, in the same order.

Grant Type Comparison

Grant typeBest fitNotes
Authorization Code + PKCEUser-facing clientsThe default for public clients
Client CredentialsService-to-service callsNo end-user delegation; defined in MCP’s Client Credentials extension, not core
Device CodeLimited-input devices and some CLI flowsUseful when browser-based sign-in is awkward

Discovery, in Order

Each step, and the URLs a client tries (→ ch19 §The First Request Usually Fails):

  1. The client calls the MCP server without a token and gets 401 Unauthorized. The server should include resource_metadata in WWW-Authenticate, and ideally a scope parameter too.
  2. The client fetches protected resource metadata from the header URL. If the header has no URL, it tries the path-suffixed well-known URI and then the root one.
  3. The client checks that the metadata’s resource value matches the MCP server’s resource identifier (RFC 9728 §3.3). It then picks an authorization server from the authorization_servers list.
  4. The client fetches authorization server metadata (RFC 8414), falling back to OpenID Connect Discovery, which MCP clients must also support. For an issuer with no path (https://auth.example.com), it tries /.well-known/oauth-authorization-server and then /.well-known/openid-configuration. For an issuer with a path (https://auth.example.com/tenant1), it tries three URLs in order: /.well-known/oauth-authorization-server/tenant1, /.well-known/openid-configuration/tenant1 and /tenant1/.well-known/openid-configuration.
  5. The client rejects any metadata document whose issuer is not identical to the issuer it used to build the URL (RFC 8414 §3.3).
  6. The client registers or identifies itself (see below), then runs Authorization Code + PKCE.

What the Bytes Look Like

These are the tickets server’s documents from Chapter 19; compare your curl -i output field by field.

Step 1 is the 401 for a request that carried no token. Leave out error when no token was sent (RFC 6750 §3.1). Add error="invalid_token" when a token was sent and rejected.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="tickets:read"
Content-Type: application/json

Step 2 is GET https://mcp.example.com/.well-known/oauth-protected-resource/mcp:

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["tickets:read", "tickets:write", "deployments:read"],
  "bearer_methods_supported": ["header"]
}

Step 3 compares resource with the configured URL byte for byte; a trailing slash or a different host fails (Chapter 22’s 401 loop).

Step-up is a 403 sent when the token is valid but a tool needs more. Here delete_deployment needs a scope the token lacks:

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="deployments:write", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"

Authorization Code + PKCE in One Screen

  1. The client creates code_verifier and code_challenge, and records the authorization server’s issuer alongside them.
  2. The user is redirected to the authorization server. The request includes resource (RFC 8707), set to the MCP server’s canonical URI.
  3. The user authenticates and grants consent.
  4. The client receives an authorization code. If the response carries iss (RFC 9207), the client compares it with the recorded issuer by simple string comparison before doing anything else. If the server advertises authorization_response_iss_parameter_supported and iss is missing, the client rejects the response.
  5. The client exchanges the code, code_verifier and resource for tokens.
  6. The client calls the protected MCP server with the access token in the Authorization header (Bearer scheme) on every request, never in the query string.

Client Credentials in One Screen

  1. A confidential client authenticates to the authorization server.
  2. The authorization server issues an access token.
  3. The client calls the protected resource as itself.

Client Registration Options

Before the flow starts, an MCP client needs a client ID. In 2026-07-28, a client that supports every option should try them in this order (→ ch19 §Registration; as a tree in Appendix L):

  1. Pre-registered credentials for that authorization server, if the client has them.
  2. Client ID Metadata Documents (CIMD), used when the authorization server advertises client_id_metadata_document_supported. The client ID is an HTTPS URL that serves a JSON document containing client_id, client_name and redirect_uris. The authorization server fetches the document and checks it.
  3. Dynamic Client Registration (RFC 7591), when the server advertises a registration_endpoint. Deprecated; a client using it must send application_type (native for desktop, CLI and localhost apps).
  4. Asking the user to enter client details.

Stored credentials are bound to their issuer; never reuse them elsewhere. CIMD client IDs are portable.

Error Handling Cheatsheet

StatusTypical meaningWhat the client should do
401 UnauthorizedMissing or invalid tokenRe-authenticate or refresh
403 ForbiddenToken valid but insufficient (error="insufficient_scope")Step up: re-authorize with the union of previously requested scopes and the challenged scopes, with a retry limit

Send resource_metadata on both 401 and 403, and one scope listing everything the operation needs. An ID token is not an access token.

Scopes Follow Annotations

Name scopes after capabilities, not roles (tickets:read, not admin_everything), and let a tool’s annotation decide which scope it needs (→ ch19 §A valid token is not a permission):

ScopeTools it unlocksAnnotation on those toolsRequested
tickets:readsearch_ticketsreadOnlyHint: trueUp front
tickets:writetools that change a ticketreadOnlyHint: falseUp front
deployments:writedelete_deploymentdestructiveHint: trueBy step-up only

Minimal Security Checklist

  • PKCE enabled for public clients
  • token expiry tested
  • refresh behavior tested where applicable
  • token validation performed server-side
  • scopes mapped to real policy
  • protected resource metadata published
  • iss validated when present in authorization responses
  • resource parameter sent in authorization and token requests; tokens checked for audience server-side

Key RFCs and Standards

  • RFC 6750: Bearer Token Usage. Used in discovery step 1 (the WWW-Authenticate challenge)
  • RFC 9728: Protected Resource Metadata. Used in discovery steps 2–3
  • RFC 8414: Authorization Server Metadata. Used in discovery steps 4–5
  • OpenID Connect Discovery 1.0. Used in discovery step 4
  • OAuth Client ID Metadata Document (IETF draft). Used in registration option 2, the preferred mechanism
  • RFC 7591: Dynamic Client Registration. Used in registration option 3 (deprecated in MCP 2026-07-28)
  • RFC 7636: PKCE. Used in Authorization Code step 1
  • RFC 8707: Resource Indicators. Used in Authorization Code steps 2 and 5
  • RFC 9207: Authorization Server Issuer Identification. Used in Authorization Code step 4
  • RFC 6749: OAuth 2.0. Used in Authorization Code steps 5–6
  • OAuth 2.1 (IETF draft): the baseline the MCP authorization spec builds on