The OAuth flow for remote servers

Only remote HTTP servers use OAuth. The MCP server is an OAuth resource server, and the client discovers everything it needs, starting from a single 401.

By the end of this lesson you can

  • follow the discovery chain from a 401 to the authorization server;
  • explain what PKCE, the resource parameter and the iss check each prevent;
  • say how a client gets an identity: pre-registration, metadata document or DCR;
  • explain audience checks, the ban on token passthrough, and step-up with 403.

From one 401 to a token

The client needs no configuration beyond the server's URL. Each answer points to the next document.

The discovery chain

Clientknows only a URL MCP serverresource server Auth serverissues tokens Browseruser signs in 1 · POST /mcp, no tokenthe client simply tries 2 · 401 + WWW-Authenticateresource_metadata = where my PRM is 3 · GET protected resource metadataauthorization_servers: [auth…] 4 · GET authorization server metadataendpoints, issuer, PKCE methods 5 · authorize in the browserPKCE challenge + resource=https://mcp.example.com 6 · code + verifier → access tokenaud: https://mcp.example.com 7 · Authorization: Bearer …on every request, validated every time token Bearer
  1. The client simply tries: POST /mcp with no token.
  2. The server answers 401. Its WWW-Authenticate header points at its metadata document and suggests minimal scopes.
  3. The client fetches Protected Resource Metadata (RFC 9728). It names the authorization server that issues tokens for this resource. Mandatory for MCP servers.
  4. The client fetches the authorization server's metadata (RFC 8414, or OpenID configuration) and records its issuer and endpoints.
  5. It opens the browser at the authorize endpoint with a PKCE challenge and the resource parameter naming this MCP server. The user signs in and consents.
  6. The code comes back; the client swaps it, with its PKCE verifier, for an access token whose audience is this one server.
  7. From now on, every request carries Authorization: Bearer …, and the server validates it every time.

Step through the whole flow

Four lanes: the browser, the MCP client, the MCP server and the authorization server. Every step shows the real HTTP request or document.

What PKCE buys you

The authorization code travels through the browser, where other software might see it. PKCE makes a stolen code useless.

A secret only the real client knows

Clientholds the verifier Auth serverstores the hash Attackersaw the code verifier = random secretchallenge = S256(verifier) authorize … code_challenge=S256(verifier) code, via the browser redirect /token + code, no verifier ✕ /token + code + verifier ✓ → access token code
  1. Before anything else, the client invents a random verifier and computes its SHA-256 hash, the challenge.
  2. The authorization request carries only the challenge (code_challenge_method=S256). The authorization server stores it.
  3. The authorization code comes back through the browser redirect, where other software may see it.
  4. An attacker who copied the code tries to redeem it, but has no verifier. Refused.
  5. The real client sends the code with the verifier; the server hashes it, matches the challenge, and issues the token.

One token, one audience

The resource parameter (RFC 8707) goes on both the authorization and the token request, so the token is minted for this one server. The server enforces the other half.

Audience and passthrough

Clientaud: mcp-a MCP server A MCP server B GitHub APIdownstream aud matches ✓ aud is mcp-a, not me ✕ forward the user's token? ✕token passthrough is forbidden its own credential for GitHub ✓a separate token, its own audience tok tok tok own
  1. The token's audience is server A. A validates it on every request and accepts it.
  2. The same token presented to server B is refused: it was not issued for B. This stops a token leaked to one server being used at another.
  3. Server A needs to call GitHub. Forwarding the user's token would be token passthrough, which the spec forbids.
  4. Instead A uses its own credential for GitHub, a separate token with its own audience and scopes.

The rules in one place

Spec rules

  • Protected Resource Metadata (RFC 9728) is mandatory: it is how the client finds the authorization server.
  • Clients support both AS discovery documents (RFC 8414 and OpenID configuration) and record the issuer.
  • Clients SHOULD use the scope in the 401 challenge first, then scopes_supported.
  • The resource parameter (RFC 8707) MUST be sent on both the authorization and token requests.
  • When the redirect carries iss, the client MUST check it against the recorded issuer (RFC 9207) to stop mix-up attacks; if the authorization server advertises iss support, a redirect without it is rejected.
  • Client registration, in order of preference (SHOULD): pre-registered details if the client already has them, then a Client ID Metadata Document (the client id is a URL the authorization server fetches), then Dynamic Client Registration as a deprecated fallback, then asking the user.
  • Servers MUST validate the token's audience on every request and MUST NOT accept or pass through tokens issued for anything else.
  • A 403 with insufficient_scope starts a step-up: the client re-authorizes asking for the union of its old and the new scopes.

Key idea

The bearer token on every request is also how a stateless replica knows who you are. No session, no sticky routing: identity arrives with each request (lesson 11).

Quick check

The client knows only https://mcp.example.com/mcp. How does it find the authorization server?

  • It is configured by the user
  • The 401 points to Protected Resource Metadata, which lists the authorization servers
  • It asks server/discover
The WWW-Authenticate header's resource_metadata points to the RFC 9728 document, whose authorization_servers names the issuer.

What does the resource parameter achieve?

  • It makes the code single-use
  • It names the one MCP server the token is for, so the token's audience is that server
  • It tells the client where to redirect
RFC 8707 resource indicators scope the token to one audience; the server then checks that audience on every request.

Mid-session the server answers 403 with insufficient_scope. What should the client do?

  • Give up: the user is not allowed
  • Retry with the same token
  • Re-authorize, asking for the union of the old and the new scopes
That is step-up authorization. Asking for the union keeps the permissions the client already had.

Your MCP server needs to call the GitHub API on the user's behalf. Can it forward the bearer token it received?

  • Yes, if the user consented
  • No: that token's audience is your server; forwarding it is forbidden token passthrough
  • Yes, if it is over HTTPS
Each token is for one audience. Get a separate credential for GitHub.

Recap