Senior · Core path · 8 chapters
MCP auth
OAuth flows, authorization servers, token audience binding, and why a token minted for your server must not be replayable against upstream APIs
MCP integrations usually fail at credential boundaries, and a token-scoping mistake can turn one compromised server into access to every upstream API.
Read these first
What you will know
Implement the OAuth flow for a remote server
Handle authorization, callback validation, token exchange, refresh, and failure paths rather than treating the access token as a static configuration value.
Reproduce an audience-binding failure by presenting a server-scoped token to an upstream API
A correctly signed token can still be unusable when its resource or audience claim does not match the receiving API.
Show why a token minted for your server must not be replayable upstream
The blast radius of a stolen credential is bounded only when each service rejects tokens issued for a different resource.
Debug an auth failure from the protocol trace
Correlate HTTP status, WWW-Authenticate challenges, issuer metadata, claims, and token-exchange steps instead of guessing from a generic MCP error.
Explain the whole flow at a whiteboard
The useful explanation connects client registration, authorization-server decisions, token validation, audience checks, and the downstream call into one trust story.
Chapters
- 1
Discovering the authorization server
A client meets a remote MCP server it has never seen and holds no credentials for. The 401 it gets back carries WWW-Authenticate, which points at the server's protected resource metadata, which names the authorization server — discovery, not configuration. From there this chapter walks the grant end to end: client registration, the authorization request, callback validation, code exchange, refresh, and the failure paths that treating an access token as static config skips entirely.
- 2
PKCE, state and redirect_uri
Three controls ride along with every authorization-code flow and none of them substitutes for another. PKCE binds the code to the client that began the flow; `state` binds the callback to the session that initiated it; redirect_uri registration binds where a code may be delivered, and it is registered at the authorization server against a client id rather than at whatever proxy sits in between. All three assume the attacker is intercepting somebody else's flow, and holding that assumption explicitly is what lets you recognise, later in this topic, an attack in which the attacker owns the flow and all three controls are satisfied.
- 3
Token audience binding
A token can be fresh, signed by the expected issuer, and working against the same vendor's REST API, and still be refused by their MCP server with error=\"invalid_token\" and a description of audience mismatch. The aud claim names a resource, and the repair belongs at issuance — the resource parameter on the authorization request — not at the server doing the checking. This chapter reproduces that failure deliberately and states what the check buys.
- 4
Building an MCP resource server
Everything so far is the client's half. From inside a remote MCP server the same flow reads differently: you publish the protected resource metadata that names your authorization server, you answer an unauthenticated call with the 401 and WWW-Authenticate that points at it, and you validate every incoming token before any tool runs — signature and issuer, the aud claim against your own resource identifier, expiry, and the scopes the specific tool requires. These are the checks the rest of the topic assumes are in place.
- 5
Token passthrough to upstream services
Your server receives a bearer token and passes it unchanged to an internal reporting API, which accepts it because both sit behind the same identity provider. That saves a grant and dissolves a boundary: anyone holding a token for your server now holds one for reporting too, and the audience check stops bounding anything. This chapter ranks the passthrough against a hardcoded Stripe key, decides which to fix first, and answers the claim that nothing was escalated.
- 6
Per-user upstream grants
The server must call Salesforce for a user who has already authenticated to it and whose tools are working. The answer is not to reuse the incoming token: the server holds its own authorization relationship with Salesforce as a client, stores those tokens per user, and keys them to the identity in the validated request. Two separate grants, no credential doing both jobs — and this is the picture you draw when asked to explain the whole trust story.
- 7
The confused deputy problem
Register one OAuth client per SaaS, share the client id across every user, and proxy the authorization flows — the fastest shape, and an exploitable one. An attacker starts the flow with a redirect_uri they control, the victim's browser already carries consent for that static client id, so the consent screen is skipped and a code lands on the attacker's host. PKCE, state validation and redirect allowlisting at your proxy are each necessary and none of them touches this. The mitigation is consent you own: your proxy asks each user to authorise it rather than inheriting an approval recorded against a shared client id.
- 8
Diagnosing 401 and 403 responses
initialize succeeds, tools/list returns, every tools/call comes back 401, and the team has already re-run OAuth twice and rotated the client secret. This chapter sets out the order to pull evidence out of the protocol trace and what each item eliminates, including why success on the earlier calls proves nothing when those endpoints are often unauthenticated. A refresh reissues what the grant already covers and never adds a scope, which is why insufficient_scope needs consent rather than a retry.
External resources
- Authorization — Model Context Protocol specification (2025-11-25)modelcontextprotocol.io
The MUSTs: audience validation, no token passthrough, resource-server split.
- Authorizing MCP serversspeakeasy.com
Step-by-step OAuth 2.1 wiring for a real MCP server.
- Resource Indicators in OAuth 2.0: A guide to RFC 8707workos.com
Explains the `resource` parameter that makes tokens non-replayable.
- Bind Every MCP Token to One Serverzuplo.com
Concrete rule for why your token must not work upstream.
- MCP Authentication and Authorization: OAuth 2.1, Token Delegation, and the Confused Deputy Problemflowhunt.io
Walks the confused-deputy attack the audience check prevents.
- [Session] Intro to OAuth for MCP Servers with Aaron Parecki, Oktayoutube.com
Named OAuth practitioner and vendor context; title directly covers MCP OAuth mechanics