Skip to content

Build1 publisher3 min readPublished

MCP servers need their RFC 9728 resource URL to reject tokens minted for other servers

MCP servers that skip audience binding accept tokens minted for other servers, says a dev.to OAuth 2.1 guide that puts it in the 80% most guides skip. The check depends on RFC 9728 metadata the server publishes and a resource parameter the client sends.

The Engineer · Build desk

Illustration accompanying MCP servers need their RFC 9728 resource URL to reject tokens minted for other servers

What happened

  • In MCP's OAuth 2.1 model, as a dev.to guide describes it, the MCP server is a resource server that validates tokens and leaves issuing them to an authorization server.
  • Clients are supposed to find that authorization server through an RFC 9728 metadata document served at /.well-known/oauth-protected-resource, with no hardcoded endpoints.
  • Each token must be bound to one MCP server's audience, so a token minted for Server A fails at Server B even when both trust the same identity provider.
  • The guide's author says teams almost always get token signature checks right and almost as often skip the audience check.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A server cannot write a meaningful audience check until it has published its own resource URL, so the metadata document has to ship before token validation does.
  • exposure Where one authorization server issues tokens for several MCP servers, a server that skips the aud check will honor tokens that users granted to a different server.
  • constraint Servers that key agent state to the access token lose in-progress multi-step workflows on refresh, so session storage needs its own TTL and idle timeout.

The three requirements are one chain [7][8]. The server publishes a `resource` URL in its metadata document [6]. The client sends that URL back as an RFC 8707 `resource` parameter when it redirects the user to the authorization server, and the authorization server writes it into the token's audience [8]. On each request, the server compares the token's `aud` claim with its own `resource` value [7]. If the first step is missing, the last has nothing to compare against [7]. The author wrote that teams skip the audience check because it requires the server to know its own identity as a resource server, and that means doing the metadata step the post puts first [15].

The metadata document is small. The example serves four fields at `/.well-known/oauth-protected-resource`: `resource`, `authorization_servers`, `bearer_methods_supported` set to `["header"]`, and `resource_documentation` [3][6]. Without it, every client is configured by hand with the authorization server's URL, and the post says that setting breaks the first time the operator rotates identity providers [9].

The middle link depends on a party the MCP server does not control [2][10]. The post says most tutorials leave the `resource` parameter out [8]. Without it, a token a user grants to one MCP server today could work against a malicious server tomorrow if the authorization server does not scope tokens per resource [10]. The server's check can only reject an audience the authorization server wrote into the token [7][8]. I think confirming that the identity provider honors resource indicators comes before any validation code.

PKCE is in the list for a different reason. The post's case is that MCP clients are often CLI tools and desktop apps that cannot safely hold a client secret [5]. Its example builds the verifier with `secrets.token_urlsafe(64)` and sends an S256 challenge [11]. The cross-server replay the post describes runs through the `resource` parameter and the audience claim [10]. It names audience confusion, in setups where one authorization server issues tokens for several MCP servers, as the most common real-world break [21].

On validation, the post says a server checks "four things, not one" [12]. Its snippet pins `algorithms=["RS256"]`, passes `audience=expected_audience` to `jwt.decode`, then tests `exp` and `aud` again by hand [13]. So the audience gets checked twice, in code written for teams that, by the author's account, almost as often skip it entirely [14].

Tokens and sessions get separate lifetimes. Tokens last 15 to 60 minutes and are validated on every request; sessions live server-side under a session ID with their own TTL and idle timeout [16]. A token that expires mid-session is refreshed without ending the session, and an expired session forces a new authorization handshake even if the token is still valid [17]. Mixing the two, the post says, is how a token refresh silently resets an agent's multi-step workflow [19]. The author wrote that "an idle session is a bigger risk surface than a rotated token" [18].

The author also wrote that skipping any of these steps gives a server that works in a demo with one client and one user and "quietly becomes a multi-tenant data leak in production" [22]. The post is a tutorial with code and does not survey deployed servers. Its claims about what most guides and most teams skip are the author's own assessments [20][14].

What to watch

  • Whether the identity providers MCP operators use support RFC 8707 resource indicators, the condition the guide's replay protection depends on.
  • Any published scan of deployed MCP servers that measures how many skip the aud check, which would test the guide's unquantified claim about what most teams skip.
  • Revisions to the MCP authorization spec that change the discovery or audience-binding requirements this guide is built on.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories