Agentic Cyber

MCP Server Authorization: Audience Binding and the Confused Deputy

By Declan Osei · October 16, 2026

Category: defensive-architecture-security-controls

MCP Server Authorization: Audience Binding and the Confused Deputy

A verified token tells you nothing about who it was issued for. How audience checks, a ban on relaying credentials, and per-client consent keep an MCP server from acting as somebody else's deputy.

Key takeaways

  1. The problem Teams read a good signature as an authorization decision, so MCP servers accept credentials cut for other services and then relay them upstream.

  2. Core insight An MCP server holds two separate OAuth relationships at once, so it needs two separate tokens: one addressed to its own canonical URI, one it obtains for itself from the upstream authorization server.

  3. Practical outcome Audience checks on every request, no relaying of inbound credentials, per-client consent at a proxy, and narrow scopes with recorded step-up turn a borrowed credential into a rejection you can alert on.

A bearer token that verifies is not the same thing as a request you are allowed to serve. Signature good, expiry in the future, scopes present and plausible: a Model Context Protocol server can tick all four boxes and still be working on a credential that an authorization server minted for a completely different API. Honour it, then relay it upstream, and the server has quietly volunteered as the deputy an attacker borrows.

This piece covers one narrow slice of MCP server authorization: who a token was issued for, and what a server owes the person behind it when it calls something else on their behalf. The specification settles the question in normative language. The draft security best practices page sorts the failure into its two recognisable shapes. Nothing below is an attack recipe. Every check named here is one a defender runs against a deployment they own.

Why a Good Signature Feels Like Enough

Most of us met OAuth from the client side. You register an application, you collect a token, you attach it to calls, and the hard parts happen somewhere you never had to read. The resource-server duty, asking whether this credential was addressed to you at all, rarely came up. MCP ends that comfort, because an HTTP server speaking this protocol is a resource server whether or not its author thought of it that way, and the Authorization section of the 2025-06-18 revision is blunt about the duty it carries:

MCP servers MUST only accept tokens specifically intended for themselves

The same requirement continues into the negative case: a server has to turn away any credential whose audience claim does not name it, or otherwise satisfy itself by some equivalent means that it really is the intended recipient. That is a check on the token's addressee, not on its cryptography, and a library that validates signatures will happily pass a token that fails it.

Convenience keeps the habit alive. A great many MCP servers exist to put a friendly tool surface in front of a third-party API, and in that shape the laziest wiring works: take whatever arrived in the inbound authorization header, copy it into the outbound request, ship the demo. The specification has a name for that wiring, token passthrough, and prohibits it outright.

What Goes Wrong Once a Proxy Server Carries Real Traffic

The draft security best practices page separates the problem into two defects that usually travel together. The first is a missing audience check, where a server honours a credential cut for some neighbouring service and so erases a boundary that OAuth was built to draw. The second is the relay itself, where that credential continues downstream and the API at the far end treats it as though an MCP server had vouched for the caller.

Three consequences follow, and each one has a different owner on a security team:

  • Protections keyed to the audience stop firing. Quotas, throttles and request validation that a downstream service applies per client do nothing for a caller who is holding that service's own credential.
  • Attribution falls apart. Your server can no longer separate one client from another, and the downstream records an identity that is not the party who actually asked. That is a direct hit to the evidence you would need later, which is the subject of what an agent audit trail has to be able to prove.
  • Compromise stops being local. When several services will take the same credential, whoever lifts it from one of them inherits the others.

There is a second, more specific confused-deputy case, and it belongs to proxy-style servers that sit in front of a third-party authorization server using a single shared client identifier. The specification sets out the conditions: one static client ID, dynamic client registration in play, a consent cookie already held at the third party, and no consent step of the server's own. Line all of those up and a client the user never agreed to can come away with an authorization code that looks consented. The documented remedy is for the proxy to record and check its own per-client consent before it hands anybody on to the third-party flow. For how this sits inside the wider picture, see our treatment of the MCP server trust boundary.

Two Relationships, Two Tokens, One Process

The mental model that fixes this is small enough to hold in your head during code review. An MCP server stands in two relationships at once. Facing the client it is a resource server, and the only credential it may accept is one addressed to its own canonical URI. Facing an upstream API it is an ordinary OAuth client with its own registration, and the credential it uses there is one it obtained for itself from that upstream authorization server. Two relationships, two tokens, and no path between them.

Say it the other way round if that lands better: inbound credentials are addressed mail. You read your own, you act on your own, and you do not re-post somebody else's with your return address on the envelope. The specification requires exactly that separation, and treats relaying the inbound credential as prohibited rather than merely discouraged.

Controls That Make the Boundary Real

Four controls carry most of the weight, and none of them is expensive.

  • Compare the audience against your canonical URI on every request, before any tool dispatch. A mismatch is a rejection, not a warning. On the issuing side, the specification obliges clients to send the RFC 8707 resource parameter, which is what lets an authorization server bind a credential to your server in the first place, so a client library that omits it is a finding of its own.
  • Mint your own upstream credential. Token exchange or your server's client credentials, never the header you were handed. In practice this means grepping your codebase for every place an inbound authorization header is copied into an outbound request, because that single line is what passthrough looks like in source form.
  • Keep scopes narrow and let privilege step up. The best practices page warns specifically against omnibus scopes of the files-everything or admin-everything variety. A low-risk base scope plus a targeted challenge the first time a privileged tool is reached gives you a far better signal, provided you record each elevation.
  • Shorten the window and bind the handles. The specification says authorization servers should issue short-lived access tokens, and requires public clients to rotate refresh tokens. Separately, if a tool accepts a workflow, session or cart identifier, tie that identifier to the authenticated subject, because holding an identifier has never been the same as proving who you are.

One more item sits adjacent and is easy to forget: discovery makes a client fetch URLs that a server supplied. Require HTTPS, and refuse private and link-local address ranges, or the discovery step becomes a request-forgery primitive. Treat the denials these controls generate as telemetry rather than noise, which is the argument in our runtime monitoring guide: a run of rejected audience checks against one server is an early signal worth paging on.

The Revision Moved, and So Did the Normative Text

Check which revision you are reading against. The protocol's versioning page names 2026-07-28 as the current revision, with identifiers carrying the date of the last backwards-incompatible change. The audience duty survived the move and got sharper. The current authorization section requires a server to validate that an access token was issued specifically for it as the intended audience, states that it must only accept tokens valid for its own resources, and closes the passthrough question in four words: it must not accept or transit any other token. On the other side of the connection, clients must not send a server any token other than one issued by that server's own authorization server.

Three requirements there belong on a review checklist, as new teeth rather than restatements.

  • Protected resource metadata is mandatory for servers. A server must implement RFC 9728 protected resource metadata, and clients must use it to discover the authorization server. A server with no metadata document is non-conforming, not merely awkward, and it forces every client to guess.
  • The resource indicator is unconditional. Clients must implement RFC 8707, must send the resource parameter in both the authorization request and the token request, must set it to the server's canonical URI, and must send it whether or not the authorization server supports it. The spec also says what a canonical URI is not: no missing scheme, no fragment, one consistent choice about the trailing slash.
  • Issuer validation on the authorization response. Clients must record the issuer from validated metadata, then apply the RFC 9207 check before the authorization code goes anywhere near a token endpoint, with no case folding or percent-encoding normalisation before the comparison. Servers are told they should include the issuer parameter, and the section states that a future revision is expected to raise that from should to must. Implement it now and the upgrade is a non-event.

Two migrations run underneath this. Dynamic client registration is deprecated and retained for backwards compatibility, with client ID metadata documents preferred, and the project's lifecycle policy keeps a deprecated feature in the specification for at least twelve months before it is eligible for removal. A server leaning on dynamic registration therefore has dated work, not open-ended work. The step-up flow is the other: a server should answer an under-scoped call with 403, an insufficient scope error and the scopes the operation needs, and a client should re-authorise with the union of its previous scopes and the challenged ones. That union requirement is the detail implementers get wrong, and getting it wrong looks like an agent mysteriously losing a permission it had an hour ago.

The Carve-Out Is Where the Credentials Actually Leak

The specification says authorization is optional, that HTTP transports should conform, and that servers speaking over standard input and output should not follow it and should take credentials from the environment instead. As protocol design that is correct: there is no browser, no redirect and no audience to bind when the client starts the server as a child process. As a deployment policy it is the most dangerous sentence on the page, and "we use STDIO, so this does not apply" is a finding rather than an answer.

Follow what the carve-out means in practice. Credentials from the environment means a token sitting in a developer's configuration file or shell profile, which in the common case is long-lived, has no audience claim to validate, no refresh rotation, and no revocation path that anybody has tested. Every control in this article is unavailable there. Worse, those files are a known collection target, which is the subject of what infostealers do with agent tokens and server configurations. The elegant audience machinery above protects the servers you expose on the network, while the credential most likely to be stolen is the one the specification explicitly declines to govern.

So the position: hold local servers to the parts of this that do apply, although the protocol does not ask you to. Short-lived credentials issued per session from a secret manager, not pasted into a configuration file. One credential per server, scoped to what that server calls, so a stolen token is not an estate. A revocation path, tested once. And an inventory that counts local servers, which arrive with whoever installed them.

Where this is overhead rather than security: a local server that holds no credential at all, a formatter, a calculator, a wrapper around files the developer already owns. There the correct answer is genuinely nothing, and asking for a secret manager in front of it wastes goodwill you will need for the servers that matter. The distinguishing question is not the transport. It is whether the process holds a credential that would be worth stealing.

Your Order of Work, Cheapest First

  1. Inventory every MCP server you operate and every one you connect to, and flag the ones whose job is to front a third-party API. That subset holds nearly all of the exposure.
  2. For each flagged server, answer two questions in writing: does it check the audience claim, and does any code path forward the inbound credential.
  3. Repair the relaying first. It is the pattern with the widest blast radius and usually the smallest diff.
  4. Rewrite wildcard scopes into a base scope with step-up challenges, and make the elevation events queryable. Scope design is where least privilege gets awkward for agents, so expect a round of argument.
  5. Prove the boundary in a test that survives refactoring. In a non-production environment, present your server with a credential issued for a different service by the same authorization server and assert a rejection. If the call succeeds, you have found a server that checks cryptography and nothing else.

Be honest about the reach of that last test. It tells you whether the boundary exists at all. It says nothing about whether your scopes are sensibly cut, or whether a perfectly legitimate credential is being used far more than its owner intended. Those questions need design review and behavioural monitoring, and no single assertion in a test suite will answer them.

Authorization is optional in this protocol, which is worth remembering when someone argues about compliance for a server that speaks over standard input and output and takes its credentials from the environment. Once you are serving HTTP, though, the normative language applies to you, and audience binding is the cheapest line of it to satisfy. This is editorial analysis rather than an audit: have a qualified engineer review your authorization paths, and your counsel review any claim you make about them, before production traffic arrives.

Frequently Asked Questions

Our MCP server validates the JWT signature and the expiry. What is still missing?

The addressee check. Signature and expiry say the credential is genuine and current, not that your server is its intended recipient. Compare the audience claim against your server's canonical URI and reject anything that does not name you.

Why can a server not simply reuse the credential it was given when it calls an upstream API?

Because that relay, which the specification calls token passthrough and prohibits, makes the upstream treat the caller as validated when nobody validated them. Obtain a separate credential for your server from the upstream authorization server instead.

What does the RFC 8707 resource parameter buy us in practice?

It is how a client names the server it wants a credential for, so the authorization server can tie that credential to one audience. Without it, nothing in the issued credential stops another service from accepting it.

Does getting audience binding right protect an agent against prompt injection?

No. Injection is an instruction-handling problem and audience binding is a credential-scoping one. What binding does is cap the damage a misrouted or stolen credential can do once injection or theft has already happened.