How MCP Authorization Works: OAuth 2.1 for AI Agents
Every week another tool ships an AI agent that reads your email, queries your database, or files your tickets. The Model Context Protocol (MCP) is the standard wiring those agents into your systems, and the first question any security review asks is the obvious one: how does the agent prove it's allowed to do that? The good news is that MCP didn't invent a new answer. Its authorization spec is a tightened profile of OAuth 2.1 — the same flow you already use for "Sign in with Google," with a few requirements made mandatory.
Here's the part most teams miss, though: the single property that keeps an agent from turning into a security hole isn't PKCE or the login screen. It's making sure the token an agent receives only works on the one server it was issued for. That's audience binding, and it's where homegrown agent auth usually breaks. Below: the three roles MCP defines, the full flow end to end, and the four requirements that actually matter — with the RFCs to back each one.
The problem: an agent acting on your behalf
When you authorize an MCP-connected agent, you hand it a token that acts as you. That token is a bearer token: whoever holds it can use it. So three failure modes show up immediately.
Token theft. If the agent logs the token, caches it on disk, or leaks it in an error report, anyone who reads that token can impersonate you against the server.
The confused deputy. An agent often talks to several backends. If a token meant for your calendar server is accepted by your email server, a malicious or buggy agent can present the calendar token to the email API and walk through a door that was never meant for it.
Token passthrough. An MCP server that forwards the token it received to a downstream API — instead of getting its own — lets that downstream service mistake the agent's token for a validated one. The MCP security guidance forbids this explicitly.
OAuth 2.1 with audience-bound tokens closes all three. The flow below is how MCP gets there.
Three roles, cleanly separated
MCP maps directly onto OAuth's roles, and the separation is the important bit:
- MCP client — the agent host (Claude Desktop, an IDE plugin, your own orchestrator). It is the OAuth client.
- MCP server — the thing exposing your tools/data. It is an OAuth 2.1 resource server. It validates tokens; it never issues them.
- Authorization server — issues tokens after the user authorizes. This is a dedicated identity provider, separate from the MCP server.
That separation matters because earlier MCP drafts let the MCP server double as its own authorization server, which meant every tool author was suddenly running token infrastructure. The current spec splits them: the MCP server points clients at a real authorization server and gets out of the token business. Your tools stay tools; identity stays with an issuer built for it.
The flow, end to end
Here's the full sequence, from an agent that has no token to one making authorized calls. Each arrow maps to a named OAuth mechanism.
Agent (client) MCP server (resource) Authorization server
| | |
| 1. request, no token | |
|--------------------------->| |
| 2. 401 + WWW-Authenticate | |
|<---------------------------| |
| 3. GET /.well-known/oauth-protected-resource |
|--------------------------->| |
| 4. metadata: which AS to use (RFC 9728) |
|<---------------------------| |
| 5. GET /.well-known/oauth-authorization-server |
|-------------------------------------------------------> |
| 6. AS metadata: endpoints, PKCE methods (RFC 8414) |
|<------------------------------------------------------- |
| 7. auth-code + PKCE + resource=<mcp-server-uri> |
|-------------------------------------------------------> |
| 8. user authorizes; code returned |
| 9. token request + code_verifier + resource |
|-------------------------------------------------------> |
| 10. access token, audience-bound to the MCP server |
|<------------------------------------------------------- |
| 11. request + Bearer token |
|--------------------------->| |
| 12. validate aud == me, then respond |
|<---------------------------| |
Steps 1–6 are discovery: the agent learns which authorization server to use and how to talk to it, without anything hardcoded. Steps 7–10 are a standard Authorization Code flow with two non-negotiable additions. Steps 11–12 are the payoff — and the audience check in step 12 is the whole point.
The four requirements that actually matter
The spec references a handful of RFCs. Four of them carry the security weight.
1. PKCE is mandatory (OAuth 2.1). Every MCP client must use PKCE on the Authorization Code flow. It binds the authorization request to the token request so a stolen code is useless to anyone but the original agent. There is no opt-out and no plain method — only S256.
2. Protected-resource metadata for discovery (RFC 9728). The MCP server must serve a document at /.well-known/oauth-protected-resource naming its authorization server(s), and return a WWW-Authenticate header on its 401 so the agent knows where to look. This is how an agent connects to a server it has never seen before with zero manual configuration. (RFC 9728)
3. Resource indicators on every request (RFC 8707). The client must send a resource parameter — the canonical URI of the target MCP server — on both the authorization request and the token request:
&resource=https%3A%2F%2Fmcp.example.com
This tells the authorization server exactly which server the token is for. (RFC 8707)
4. Audience-bound tokens, validated on arrival. The authorization server issues an access token whose audience is that resource, and the MCP server must verify the token was issued for it before doing any work. A token minted for the calendar server is rejected by the email server. This is the line that kills the confused deputy.
Dynamic Client Registration (RFC 7591) is a should, not a must: it lets agents register with an authorization server they've never met, instead of every user pasting a client ID by hand. For an open agent ecosystem, you want it.
The part most teams get wrong: audience binding
Roll your own agent auth and you'll almost always nail the login screen and forget the audience. The token comes back, it's a valid JWT, the agent attaches it as a Bearer token, calls succeed. Ship it.
The problem only surfaces when a second backend enters the picture. Now you have a token that any of your services will accept, carried by an agent that talks to all of them. That's not a hypothetical — it's the confused-deputy problem, and it's why MCP makes both halves mandatory: the client must name the resource (RFC 8707), and the resource must check it was the named one. Skip either half and you've built a master key.
Audience binding is cheap to do and easy to skip, which is exactly why a spec had to require it.
The OAuth 2.1 baseline, and where NamoID fits
Notice what the MCP profile assumes underneath all of this: a "safe by default" OAuth 2.1 authorization server. PKCE on every flow. No implicit grant and no password grant to disable. Refresh tokens that rotate. Exact redirect-URI matching. If you start from a permissive OAuth 2.0 server, adopting MCP means turning off several insecure defaults first. (We wrote up the full list of what changed in OAuth 2.1 vs 2.0.)
NamoID starts from the other end. It's an OAuth 2.1 issuer built MCP-ready:
- PKCE on every authorization flow, first-party clients included — there's no flow that skips it.
- No implicit or password grant. The only grants are
authorization_codeandrefresh_token, so there's nothing insecure to switch off. - Refresh-token rotation with replay detection — a reused refresh token revokes the chain.
- Resource-indicator audience binding (RFC 8707). Tokens are minted for a named MCP server and rejected everywhere else.
- Dynamic client registration (RFC 7591), so agents can onboard without a human pasting credentials.
You point your MCP server's protected-resource metadata at NamoID, and the agent flow above just works — with the audience check that makes it safe, not just functional.
FAQ
Does MCP define its own auth protocol? No. It's a profile of OAuth 2.1 plus four established RFCs (8707, 9728, 8414, 7591). If you know OAuth, you know MCP auth.
Is the MCP server the authorization server? No — and this is the most common misread. The MCP server is a resource server. It validates tokens and points clients at a separate authorization server via protected-resource metadata.
Why a resource parameter if the token already has scopes? Scopes say what the agent may do; the resource says where the token is valid. Without it, a token leaks across services. You need both.
Do I need Dynamic Client Registration? Only if agents you don't control will connect. It's a should, not a must — but for an open ecosystem it removes real friction.
Build agents on an issuer that's already safe
MCP authorization is OAuth 2.1 done strictly: PKCE, discovery, resource indicators, and audience-bound tokens, with no insecure defaults to remember to disable. Get the audience binding right and the agent that reads your data can't quietly become the agent that reads everyone's.
NamoID gives you that issuer out of the box. If you're building an MCP server and want an authorization server that speaks the profile natively, start with the OAuth 2.1 overview or the single OIDC issuer NamoID gives you to put in front of it.