MCP OAuth and Credential Handling Fundamentals
OAuth 2.1 replaces static keys with role separation, PKCE, and tokens bound to single servers.

Before MCP had a formal authorization standard, most deployments ran on static API keys sitting in mcp.json files or.env variables. If a key leaked, it handed over full access to whatever system it touched, and no one noticed or revoked it until someone did so by hand. There was no log of what the key had been used for, no expiry date, no way to tie the activity back to a specific person. This wasn't a hypothetical risk sitting in a threat model somewhere. A vulnerability in the mcp-remote package, rated CVSS 9.6, showed attackers could take the OAuth flow built into the package and use it to run arbitrary operating system commands on the client machine, and the package had been downloaded hundreds of thousands of times.
The fix didn't arrive all at once. The specification moved through three distinct revisions: March 2025 set an OAuth 2.1 baseline, June 2025 introduced the resource server model along with Protected Resource Metadata, and November 2025 made PKCE with S256 mandatory and introduced Client ID Metadata Documents. Each revision closed a gap the one before it had left open.
The four roles the spec keeps strictly separate
MCP's authorization model rests on four roles that the spec insists stay separate: the MCP client, the MCP server, the authorization server, and the user. Where real-world deployments run into trouble, it's almost always because an implementation collapsed two of these roles into one.
An employee opens an AI assistant and asks it to pull a file from a shared drive. Or a scheduled agent runs periodically to reconcile invoices, with no human watching. A company's identity team decides, in advance, which tools any employee's assistant is allowed to touch. Each of these situations needs a different combination of the four roles, and the spec keeps them distinct for exactly that reason.
The MCP client is the agent or application itself: it's an OAuth 2.1 client, and it starts authorization flows and holds onto tokens. The MCP server is an OAuth 2.1 resource server: it checks tokens for validity, but it never issues them, because that simply isn't its job. The authorization server is the only party allowed to issue tokens, and it has to be discovered by the client rather than hardcoded or assumed. The user shows up in flows where a human delegates authority to the agent, and is absent entirely in machine-to-machine flows, a distinction that decides which grant type applies before any other technical detail matters.
One more structural change matters here: as of the 2026-07-28 revision, MCP dropped protocol-level sessions altogether. There's no more initialize handshake, no more Mcp-Session-Id header. Each request now stands alone, and it carries its own protocol version, its own client identity, and its own capabilities. That statelessness is part of what makes the role separation enforceable request by request, rather than something negotiated once and trusted forever.
The three authorization patterns the spec defines
Picking the wrong one for a given deployment counts as a security misconfiguration in its own right, not just a design preference.
User-delegated access runs on Authorization Code plus PKCE. The client acts on behalf of a real person, so that person's consent and identity bind the token's authority, and the agent can reach no further than the user's own access goes. This is the pattern for the employee asking their assistant to pull a file: the assistant can only touch what that employee could touch directly.
Machine-to-machine access covers the unattended agent, the nightly reconciliation job with no browser and no human in the loop. The optional OAuth Client Credentials extension supports this with either reusable client secrets or signed JWT assertions, and signed assertions are the recommended choice over secrets. Running a user-delegated Authorization Code flow against this scenario fails immediately: there's no human available to approve anything.
A human stays at the controls of the client, but the organization's identity provider decides in advance which servers and scopes that human can reach, through the optional Enterprise-Managed Authorization extension. This is the pattern that plugs into single sign-on, SCIM provisioning, and centrally enforced policy. Running a user-delegated flow where enterprise-managed access was called for loses the attribution and central policy control the organization actually needed.
One transport sits outside all three patterns. STDIO-transport servers inherit their credentials from the runtime environment they're running in rather than completing an OAuth flow at all, because OAuth 2.1 under this spec applies only to HTTP-based transports.
Locking a token to one server with Protected Resource Metadata and resource indicators
Two mechanisms, working together, answer the question of which authorization server a given MCP server should trust, and what a token issued by that server is actually good for. Protected Resource Metadata and resource indicators remove the guesswork, and when both are implemented correctly, a stolen token is worth exactly one resource and nothing more.
Start with discovery. Under the 2026-07-28 revision, an MCP server must publish a document at /.well-known/oauth-protected-resource, following RFC 9728, naming itself, listing which authorization servers are allowed to issue tokens for it, and stating which scopes it supports. If a client shows up without a valid token, it gets a 401 response back, with a WWW-Authenticate header pointing to the resource_metadata URL. Discovery can begin from that pointer, or fall back to the RFC 9728 well-known URI directly, and clients are required to support both paths.
Discovery alone doesn't bind a token to a server; that job belongs to the resource parameter defined in RFC 8707. So the MCP specification requires clients to include this parameter on both the authorization request and the token request. RFC 8707 itself only marks the parameter optional, but MCP clients have to send it even when the authorization server doesn't advertise support for it. On the receiving end, the MCP server must reject any token that wasn't issued for itself, and that audience check is what turns resource binding from a paper rule into something enforced at the moment a request actually arrives.
Skipping either half removes the protection. Without resource indicators, a token minted for one MCP server can be replayed against a different one entirely, and one analysis of the gap treats this as a genuine control failure rather than a small interoperability wrinkle, since it makes an incident harder to scope and its blast radius harder to bound. A walkthrough of the spec's own flow identifies the two steps practitioners skip most often: fetching the protected resource metadata, which leaves the client guessing at which authorization server to trust, and attaching the resource parameter to the token request, which leaves the token with no bound audience and makes the server's audience check impossible to enforce.
How PKCE works in MCP
PKCE solves a specific problem: an authorization code intercepted in transit, usable by whoever intercepted it. The mechanics are precise. The client generates a cryptographically random code_verifier of sufficient length from the unreserved character set, derives a code_challenge as BASE64URL(SHA-256(ASCII(code_verifier))), and sends only that challenge along with the authorization request.
The verifier itself stays home. When the client redeems the authorization code, it hands over the real verifier, and the authorization server checks that hashing it reproduces the stored challenge before issuing any token. Because the verifier never travels through the redirect, the step where codes are most exposed to interception never carries the one piece of information an attacker would need to use a stolen code (building on).
MCP narrows the standard further than plain OAuth 2.1 does. Only the S256 method is allowed, so you must use S256 whenever you can, and plain is off the table whenever S256 is technically possible. Clients also can't assume PKCE support exists: if code_challenge_methods_supported is missing from the authorization server's metadata, the client must refuse to proceed rather than quietly falling back to something weaker. This protection matters most for public clients: desktop applications, command-line tools, serverless functions, none of which can safely hold onto a long-lived client secret.
PKCE has a clear boundary. It protects the exchange as it crosses the network. PKCE says nothing about the software that generated and stored the verifier in the first place, and if the agent runtime itself is compromised, the verifier sitting inside it is compromised too, since PKCE was never designed to stop that.
Client registration after the deprecation of Dynamic Client Registration
The 2026-07-28 revision changes how a client establishes itself with an authorization server it has no prior relationship with, and this is the change most likely to break systems built against earlier guidance. Dynamic Client Registration moves from the primary path to a deprecated one, and Client ID Metadata Documents replace it.
RFC 7591 defined Dynamic Client Registration, so an MCP client could register itself at runtime with no pre-existing relationship to the authorization server. The current spec keeps DCR alive as a MAY for backward compatibility, but marks it deprecated for anything being built new.
Client ID Metadata Documents take over as the SHOULD. A client publishes its metadata at a public HTTPS URL it controls, and uses that URL itself as its client_id. The authorization server fetches the document at runtime, and it confirms the client_id matches the URL before it trusts anything in it. Building new today means building on CIMD.
For anything already running on DCR, nothing breaks immediately. A client that only speaks DCR depends on a mechanism new authorization servers aren't being told to prioritize, so this risk surfaces later as silent incompatibility rather than an error message today. Part of the risk is that plenty of existing guidance hasn't caught up. Documentation written against the March 2025 or June 2025 revisions still recommends DCR, and teams following those guides are building toward a path the current spec has already marked deprecated.
Why token passthrough is forbidden
The spec contains one prohibition that catches most practitioners off guard the first time they read it: an MCP server must never take the token it received from an MCP client and pass it straight through to an upstream API. This is the spec's core defense against credential aggregation attacks, so don't treat the ban as a minor compliance detail. Violating it turns every MCP server into a potential pivot point for an attacker who compromises just one token.
The text itself is direct: the MCP server must not pass through the token it received from the MCP client. Instead, the server validates that token, acts on the client's behalf, and presents its own separate credentials to whatever upstream system it needs to reach. The threat this closes off is specific. If a token issued with a narrow scope for a single resource gets forwarded upstream, it might carry authority that the upstream system honors even though the MCP authorization policy never meant to grant it.
The risk compounds because of how MCP servers tend to be built. A single server often holds credentials for several different systems at once, and passthrough would let a token handed to that one server travel across all of them. If you enforce the ban properly, the MCP server has to maintain its own set of upstream credentials, scoped correctly for each system it talks to. That requirement is the reason enterprise deployments need real credential management infrastructure operating alongside the OAuth flows, not just the flows themselves.
The credential crisis: the gap between specification and deployment
The specification, read end to end, holds together. What's running in production often doesn't match it. The authorization architecture itself is mature, layered, and internally consistent, but adoption lags behind it badly enough that the gap between the two is where most of the real risk sits.
Part of the problem is sheer persistence. Credentials confirmed as leaked years earlier were still active and still exploitable into 2026. Rotation reminders existed, governance tools existed, and detection alerts fired, but none of it closed the gap. That's not a failure of awareness. Teams know credentials leak and know they need rotating. The failure is operational: the bar the spec sets for a correct implementation is higher than what most existing OAuth stacks were ever built to clear, and bridging that difference takes sustained engineering work, not a single patch or policy memo.
This is the structural reason the threat landscape around MCP looks the way it does. The architecture answers the problems it was designed to answer. The attacks that succeed against MCP deployments mostly succeed by finding the places where implementation fell short of the design, not by finding a flaw in the design itself.
The six threat classes that exploit credential and authorization gaps in MCP
The threats aimed at MCP deployments aren't just familiar web API attacks with a new label on them. Several of them depend entirely on the specific relationship between an agent and the tools it calls, and defending against each one starts with understanding the mechanism that makes it possible.
Tool poisoning is one of the clearest examples. An attacker hides malicious instructions inside a tool's description or its schema, text the model reads when it decides how to use a tool, text you as the human user never see. A 2026 security analysis found that real-world tool poisoning succeeded against major LLM agents more than 60% of the time. The damage runs through a tool call that looks completely ordinary to the person watching: data gets exfiltrated, or the wrong action gets triggered, while nothing about the interaction looks out of place on the surface.
That gap between what the model reads and what the human sees is the same structural weakness running through the rest of this piece: a key with no expiry, a token with no bound audience, a client registered against a path that's already deprecated, a server passing through a credential it was never supposed to forward. Each one is a point where the architecture's intent and its actual enforcement came apart. Closing that gap, server by server and flow by flow, is what separates an MCP deployment that matches the spec from one that only looks like it does.
Sources
- MCP authentication and authorization implementation guide
- den.dev
- The state of MCP security in 2026
- A Formal Security Framework for MCP-Based AI Agents: Threat Taxonomy, Verification Models, and Defense Mechanisms
- modelcontextprotocol/docs/specification/2025-03-26/basic/authorization.mdx at main · modelcontextprotocol/modelcontextprotocol
- Model Context Protocol (MCP) Security
- Authorization - Model Context Protocol
- How MCP Authorization Actually Works: OAuth 2.1, Resource Servers, and Resource Indicators


