The threat model OAuth never had
Classic OAuth deployments have a comfortable shape: one application talks to one authorization server it was configured for, by a developer, in advance. Every trust relationship is established before the first request.
MCP breaks every clause of that sentence. A general-purpose client — an agent, an IDE, a chat app — connects to servers its developers have never heard of, at runtime, on a user's say-so. Some of those servers will be misconfigured. Some will be malicious. The client can't pre-register anywhere, the server can't vet clients in advance, and the same client juggles tokens for many servers and many authorization servers simultaneously. That last part matters: entire attack classes that were theoretical in classic OAuth — token replay across services, authorization-server mix-up, the confused deputy — are the default weather in MCP's topology.
The 2026-07-28 release candidate is where the spec finishes reorganizing itself around that reality. It isn't a new protocol — it's a profile of OAuth 2.1 (still an IETF draft, though one that largely consolidates already-stable practice) with a small set of RFCs promoted from "good idea" to "mandatory," plus a transport change that has bigger authorization consequences than most of the coverage acknowledges. This article assumes you know roughly how OAuth works; if you want the mechanics from first principles, we've published a set of interactive primers (browser flows, headless flows, MCP authorization end-to-end) that this piece builds on.
Five changes deserve your attention. Two of them will break running code.
1 · The session is gone. Per-request auth is all that's left.
The RC removes the initialize/initialized handshake (SEP-2575) and the Mcp-Session-Id header along with protocol-level sessions (SEP-2567). Protocol version, client info, and capabilities now travel in namespaced _meta fields (io.modelcontextprotocol/*) on every request, and new HTTP headers are required so gateways and rate-limiters can route on the operation without parsing bodies (SEP-2243): Mcp-Method on every request, plus Mcp-Name where the operation targets a named thing (tools/call, resources/read, prompts/get). Servers reject requests where headers and body disagree.
This is usually described as a scaling win, and it is — any request can land on any server instance, no sticky routing, no shared session store. But read it as an authorization change and it has teeth:
The dead pattern: validate the bearer token once at initialize, stash the auth context in session state, and trust the session for every subsequent call. A large fraction of early MCP servers — including SDK examples people copied — work exactly this way. Under the RC there is no initialize to hook and no session to stash into. Every request must be independently authenticated and authorized. If your server caches "who is this" anywhere except the token itself, you have a migration item.
The flip side is a genuine security upgrade you get for free: there is no session state to hijack, fixate, or leak across tenants. The bearer token — short-lived, audience-bound, carried in the Authorization header on every request, never in a URL — is the entire security context. Design your server so that a request plus its token is a complete, self-contained authorization question, and statelessness stops being a migration burden and starts being the simplest thing that works.
Here's first contact under the new rules, condensed — an MCP client meeting a protected server it has never seen (step through; dashed arrows ride the browser, solid are server-to-server):
2 · Your client is now a URL
The second breaking-ish change is quieter: Dynamic Client Registration is deprecated in MCP's authorization profile (RFC 7591 itself lives on outside MCP, and DCR remains available as a compatibility fallback). Its replacement, Client ID Metadata Documents (CIMD), changes what a client is: the client_id becomes an HTTPS URL pointing at a JSON document the client's vendor hosts — name, redirect URIs, grant types. When an authorization server sees a URL-shaped client_id, it fetches the document, validates it, and caches it. Registration collapses into dereferencing.
Why? DCR solved the stranger problem by letting anyone register — which meant authorization servers accumulated unbounded tables of anonymous clients with no trust signal and no cleanup story. Every AS operator who shipped DCR for MCP discovered they were running an open write endpoint to their client database. CIMD inverts the burden: the client's identity lives at a domain the client's vendor controls and demonstrably owns, the AS stores nothing until it has something worth caching, and one identity works across every AS (a CIMD client_id is portable; DCR credentials never were).
The details that will bite you:
- The selection priority is not what you'd guess. Clients that support everything should use: pre-registered credentials if they already hold them for this AS → CIMD if the AS advertises
client_id_metadata_document_supported→ DCR as legacy fallback → prompting the user. Pre-registration outranks CIMD — you don't re-introduce yourself to an AS that already knows you. - If you ship a client, you now run identity infrastructure. The metadata document's URL is your identity: it must be stable (changing it is changing who you are), served over HTTPS, and its
client_idfield must match its own URL exactly. Treat it like a published API contract, not a config file. - If you run an AS, you need a trust policy for client domains. Anyone can host a metadata document; dereferencing tells you the identity is coherent, not that it's trustworthy. Decide what your consent screen shows for unknown domains, and remember lookalike URLs (
claude-ai.example.net) are the new phishing surface — the spec's security considerations are worth reading twice here. - One DCR fix matters even during the transition: clients must now declare an OIDC
application_typewhen registering (SEP-837; thewebvsnativedistinction is OIDC Core 1.0 §3.1.2.1). The old default —"web"— made OIDC-strict servers reject the loopback redirect URIs that desktop and CLI clients rely on (loopback redirects are anative-client allowance under RFC 8252 §7.3). If your client registers via DCR and works against some ASes but not others, this was probably why.
3 · Tokens with a blast radius of one
Three mandates work together to make any single stolen or misused token nearly worthless. None is novel cryptography; all are old RFCs promoted to MUST because MCP's topology makes their absence exploitable.
Resource indicators (RFC 8707). Clients must send resource=<canonical server URI> in both the authorization and token requests (RFC 8707 asks the authorization server to then audience-restrict the token); MCP goes further and requires the server to reject any token whose audience isn't itself (the resource-server check itself is OAuth 2.1 §5.2; and when the access token is a JWT, RFC 9068 §4 makes that rejection an explicit MUST). This is the confused-deputy defense: without it, a malicious MCP server that receives your token can replay it against every other MCP server you use. With it, a token for server X is inert everywhere else. The operational catch: many authorization servers silently ignore resource and mint audience-less tokens. The client did everything right; the token is still promiscuous. If you're choosing or operating the AS, audience-binding support is a hard requirement, not a nice-to-have — and if you're building a server, reject tokens whose aud isn't exactly you, even if they validate otherwise.
No token passthrough. An MCP server must accept only tokens minted for it, and must never forward the client's token to upstream APIs. If your server needs to call downstream services on the user's behalf, mint a new credential for that hop (token exchange, RFC 8693, is the standard shape). Passing the inbound token upstream feels convenient and works in demos; it also makes your server a token-laundering device and destroys the audit chain. This is the rule we see violated most often in the wild.
Issuer validation (RFC 9207). Clients must record which authorization server they expect before opening the browser, then verify the iss parameter on the callback — byte-for-byte, no URL normalization — whenever it's present. (There's an asymmetry worth knowing: the client's validation is a MUST, but the AS's inclusion of iss is still only a SHOULD today, one the spec expects to tighten to MUST — so validate it whenever the AS sends or advertises it.) This kills mix-up attacks, where one authorization server (or one rogue MCP server's metadata) answers a flow another one started. In classic OAuth, mix-up was exotic because clients talked to one AS. An MCP client talks to many, concurrently, some discovered five seconds ago — which is why the RC hardens this (SEP-2468) and why the validation applies even to error responses.
4 · 403 is an API now
The spec's scope-challenge design deserves more attention than it gets, because it turns authorization failures from dead ends into a protocol. When a request needs a scope the token doesn't carry, the server should respond with 403 and a WWW-Authenticate challenge naming the scope(s) the operation requires (the spec makes this a SHOULD, and lets a server include related scopes beyond the bare minimum to save round-trips):
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="config:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"The client then runs step-up authorization: re-authorize requesting the union of its previously granted scopes plus the challenged ones (union, not just the new scope — otherwise you trade away permissions you already had), retry a bounded number of times, and treat repeated failure as permanent.
Two consequences worth designing for:
- For server builders: your 401 challenge should advertise a minimal baseline scope set, and your 403s should name all scopes the failing operation needs in one challenge — dribbling them out one per retry forces multiple authorization round-trips per operation. Be consistent; clients will encode assumptions about your challenge behavior.
- For product people: this is progressive authorization as a UX primitive. Clients no longer need to demand every conceivable permission upfront "just in case" — they can start minimal and escalate exactly when a user asks for something more. The vendors who treat scope design as API design (small, composable, named for what users understand) will feel noticeably better to use than the ones who ship one mega-scope.
5 · Scopes are not entitlements (the multi-tenant section)
Here is the part the spec doesn't cover at all, and the first thing every SaaS vendor hits in implementation: OAuth scopes and your product's permission system are different layers, and conflating them produces security bugs in both directions.
Think of two independent gates. Gate 1 — entitlements: what the customer's admin enabled for this user in your tenant model (role, plan, product tier). Lives in your database, decided long before any token existed. Gate 2 — scopes: what this particular client session was granted — the intersection of what the client requested and what Gate 1 allows. Lives in the token. Effective permission is the intersection of both, and the ordering matters: user consent operates strictly inside the box the admin drew. No amount of clicking "Approve" on a consent screen can mint a scope the tenant's entitlements don't back — and if your authorization server's consent flow doesn't consult entitlements at mint time, you've built exactly that bug.
The reverse bug is subtler: treating the token's scopes as the only check and skipping your runtime entitlement enforcement. Entitlements change mid-token-lifetime — plans downgrade, admins revoke roles, users get offboarded. A one-hour token minted under yesterday's entitlements is a one-hour window of unauthorized access unless your resource server keeps enforcing product-level checks per request. Scopes are a projection of entitlements at mint time, not a replacement for checking them.
Watch both gates operate — a permitted call, a denied one where step-up runs and the admin's decision correctly overrules the user's consent, and the silent refresh that keeps sessions alive across a workday:
Design rule that falls out: keep one system of record for permissions (your existing RBAC/entitlements), and make scopes a materialized projection of it at token-mint time — never a second, parallel permission system that can drift. Your MCP scope table should be derivable, mechanically, from the permission model you already have.
6 · What's still yours to design
Equally important is what the spec deliberately leaves out. Knowing the boundary saves you from waiting for guidance that isn't coming:
- Headless and service-principal access. The MCP authorization flow is user-delegation-shaped. An unattended agent, a CI job, or service-to-service integration is plain OAuth client-credentials territory — supported by your AS, invisible to the MCP spec. You'll need a policy for which tools a machine identity may call, because there's no consent screen to lean on.
- Multi-hop identity. When your MCP server calls further services as the user, the no-passthrough rule tells you what not to do, but the positive design — token exchange (RFC 8693), with
actclaims preserving the delegation chain — is yours to build. - Context beyond identity. Tenancy, workspace, environment, project — whatever your product's blast-radius dimension is, the spec gives you
suband scopes and stops. Our take: put tenant in a token claim (it's identity-shaped and consent-time-stable), and pass finer-grained context as validated tool arguments — never bake into the token anything a user switches between tasks. - Revocation latency. Offline JWT validation means a revoked user keeps working until token expiry. Pick TTLs accordingly (an hour is a defensible default), keep refresh-token rotation on, and keep enforcing entitlements per request (see §5) so the blast radius of the gap is product-level, not tenant-level.
7 · The migration checklist
| Change | If you build a client | If you build a server | If you run the AS |
|---|---|---|---|
| Sessions removed | Send _meta + Mcp-Method/Mcp-Name on every request; drop initialize-based lifecycles | Validate the token on every request; delete session-cached auth context; reject header/body mismatches | — |
| CIMD replaces DCR | Host a stable HTTPS metadata document; implement the pre-reg → CIMD → DCR priority; declare application_type in any DCR fallback | — | Advertise + implement CIMD (fetch, validate, cache); define a client-domain trust policy; keep DCR only as a consciously legacy path |
| Resource indicators | Send resource= on authorize and token requests, always | Reject any token whose aud isn't exactly your canonical URI | Honor resource and mint audience-bound tokens — silently ignoring it undermines everyone downstream |
| Issuer validation | Record the expected issuer pre-redirect; verify iss byte-for-byte on callbacks, including error responses | — | Include iss on authorization responses; advertise it in metadata |
| Scope challenges | Implement step-up with scope union + retry limits | Emit complete, consistent scope challenges; advertise baseline scopes on 401 | Support incremental consent without re-prompting for already-granted scopes |
| No passthrough | Never send a token anywhere except the server it was minted for | Never forward inbound tokens upstream; use token exchange for downstream hops | Consider RFC 8693 support — your vendors will need it |
| Scopes vs entitlements | — | Keep enforcing product entitlements per request; scopes don't replace them | Consult entitlements at mint time; consent must not expand beyond admin grants |
The two items that genuinely break running code are the session removal (architectural, if you cached auth state) and DCR deprecation (operational, on the AS side). Everything else is tightening.
One summary you'll hear elsewhere: "MCP now just uses OAuth, nothing to see." The protocol is standard; the deployment topology isn't, and the topology is where the work lives. One client, many servers, mutual strangers, some hostile — every mandate above exists because someone worked out how that topology gets exploited. Prepare for the topology, not just the spec.
Going deeper
- The MCP 2026-07-28 release candidate announcement and the authorization specification. The primary sources; every SEP referenced above links from the announcement.
- Our interactive primer series — OAuth 2.0 & OIDC, Headless OAuth 2.0, MCP Authorization. Step-through animated walkthroughs of every flow this article assumes: the authorization-code dance, client credentials, device flow, token exchange, and MCP first contact end-to-end.
- RFC 9728 · RFC 8707 · RFC 9207 · RFC 8693 · CIMD draft. The load-bearing standards, in order of how often you'll cite them in code review.