CSE Roadmap

Core tier · about 8 hours · 7 parts

Part 2 of 7 · 8 min read

What MCP authorisation is, and when it applies

Covers: What MCP authorisation is, and when it applies; The flow, in order; What the server must check.

What MCP authorisation is, and when it applies

The specification makes authorization optional. When a server uses an HTTP transport, it should follow the authorization specification. When it uses stdio, it should not: the process is started by the host and inherits the user's rights, so it takes credentials from the environment instead. (This is module 3's point: the stdio server's security is the security of the program you chose to run; section 9 returns to it.)

The roles are the standard OAuth ones:

OAuth role In MCP Duty
Resource server the MCP server accepts access tokens, answers 401 and 403, publishes metadata saying which authorization server to use
Client the MCP client (the host's connector) runs the flow on behalf of the person; holds the token
Authorization server a separate service (or the same host) signs the person in, asks for consent, issues tokens for one named resource
Resource owner the person decides

The specification builds on OAuth 2.1 (a consolidation of 2.0 with its security practice folded in: PKCE for everyone, no tokens in URLs, exact redirect matching) plus a few companion standards: Protected Resource Metadata (RFC 9728), Authorization Server Metadata (RFC 8414), Resource Indicators (RFC 8707), Issuer Identification (RFC 9207) and Client ID Metadata Documents. You do not need to memorise them. You need to know what job each one does, which is what the next section shows.

The flow, in order

AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTozMzdmMGY3ZS1lOTQ0LTQ1YWEtODQxMi04NDJiNWY0NzVhNmYAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaPyR6Bvc2sKjNnVBn5tUQpAAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpmNTk5MjNkYi03NzFhLTQ2MGMtYmExMC04NjllYjBlN2ZhNmNscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNov1vKOdYJr5GS+vAJ3rwfegAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFgg+tLInWHK88olikEIt/QZSJbe4gl/i7KvXl7a0LiqILmkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaDeoISg7a15mVJyc5Ah8IWkAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDsLSYyg/PkEnJ6LTcSUCPxnTgNlxGE+vuR7y+7o1q0rmRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBioZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOjMzN2YwZjdlLWU5NDQtNDVhYS04NDEyLTg0MmI1ZjQ3NWE2Zi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjY3MTZiYTNjLWU3MjgtNDNkMy1hNjFlLTA3NjJkMGE5YjBjMHJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCD60sidYcrzyiWKQQi39BlIlt7iCX+Lsq9eXtrQuKoguaJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggeIAGLYuIcnXQrBuz56ftSMrzlbUMg+1MdUtzj21KYQiiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggt2VKOFg3Cs4TBIHYzda+k+Z5LMNyYSsPDcHUqpz/q5l0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQPcKHcFZJVxqa8K3cO07VzbrhZ/jKenX7pyaB0Ds92nIhF5aeJzobErD6yJqjcjWFqo+2F/rjCka/qWMRnsuVOM=From a 401 to a working call: what the client does, in orderClientMCP serverAuth serverPerson1 call without a token2 401 + WWW-Authenticate (resource_metadata, scope)3 GET protected resource metadata4 GET authorization server metadata (issuer check, S256)5 open authorize URL: PKCE, state, resource, scope6 consent (shows client name and redirect)7 redirect back: code, state, iss8 token request: code_verifier, resource9 access token (audience = this server, short life)10 call again with Authorization: Bearer11 403 insufficient_scope (scope=...) -> union, repeat 4-10Measured for one tool call: MCP statuses [401, 200], 1 authorization, 1 consent screen.Requests by host: mcp.example.com 3, auth.example.com 1, app.example.com 1.The client sends the token only to the MCP server. Every URL it fetched in steps 3-4 was chosen by a server, so it goes through a guard (see the SSRF figure).
From a 401 to a working call. Steps 3 and 4 fetch URLs that a server chose, so they are guarded

Step 1 and 2: the first call has no token, and the answer says where to go. The server answers 401 Unauthorized with a WWW-Authenticate header. Measured on a real socket in the example:

HTTP/1.1 401
WWW-Authenticate: Bearer scope="notes:read",
    resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"

resource_metadata points at the server's Protected Resource Metadata: a small JSON document that says which authorization server issues tokens for this resource, which scopes exist, and how a bearer token may be presented. scope is the minimum needed to start. A server must serve the metadata; a client must be able to read the header and fall back to well-known addresses (first the path of the endpoint, then the root) when the header has no resource_metadata.

Step 4: find the authorization server's own metadata and distrust it a little. The client tries addresses in a set order (for an issuer with a path, three of them: OAuth path-insertion, OpenID path-insertion, OpenID path-appending). It then checks that the issuer inside the document is identical to the issuer it used to build the address. The page gives the reason as an example: a document fetched from attacker.example that says "issuer": "https://honest.example" must be rejected. The client must also check that the server offers PKCE with S256; if code_challenge_methods_supported is missing, the client must refuse to continue. Measured: a document claiming another server's issuer was rejected, and a server without S256 was refused.

Who is the client? The authorization server has never heard of this client. The specification lists three ways to answer, in order of preference: pre-registered details if the client has them; a Client ID Metadata Document, where the client's id is an HTTPS URL and the server fetches a JSON document from it (name, redirect addresses); and Dynamic Client Registration, which is now deprecated and kept for older servers. The metadata-document route removes the registration step, and it brings its own risks (the authorization server fetches a URL from a stranger, section 8; and a document at a web address cannot prove which program is listening on localhost, section 7).

Step 5 to 7: the authorization request. The client creates a one-time secret (the PKCE verifier), sends only its hash (the challenge, method S256), a random state, the resource (the MCP server's canonical address) and the scope, and opens the authorization URL in the person's browser. The person sees the client's name and where the result will be sent, and consents. The server redirects back with a short-lived, single-use code, the same state and an iss parameter naming the authorization server.

Step 8 and 9: the token request. The client sends the code and the original verifier, plus resource again. The server hashes the verifier and compares. The token it issues carries the audience (the resource the client named), the scope, the subject (the person) and a short lifetime (300 seconds in the example).

Step 10: use it. Authorization: Bearer <token> on every request. Never in the URL query: URLs end up in logs, referrers and browser history. The client sends the token only to the MCP server's own address; the spec adds that clients must not send tokens that were not issued by that server's authorization server, and servers must not accept or pass on any token not meant for them.

Measured for one tool call from nothing: MCP statuses [401, 200], 1 authorization, 1 consent screen, and requests by host of 3 to the MCP server (the first call, the metadata fetch, the retry), 1 to the authorization server and 1 to the client's own metadata document.

Tip: the resource parameter is what makes tokens one-purpose

Without resource, an authorization server may issue a token good for anything it protects. With it, the token's audience is the one server the client named. The client must send it whether or not the server supports it, because the day a server does start enforcing audiences, every client that sent it is already correct.

What the server must check

A token is only a string. Everything that makes it meaningful is a check the server makes. The example server makes five, and each has a switch so you can see what happens without it.

Check Fails when Result
signature forged or signed by someone else 401 invalid_token
issuer issued by an authorization server this resource does not trust 401
audience issued for another service 401
expiry too old 401
scope valid, but not enough for this call 403 insufficient_scope

Measured, the careful server against eight presented tokens: no token 401; garbage, wrong key, wrong issuer, wrong audience, no audience at all, expired: all 401 invalid_token; a valid token 200. The status codes follow the page: 401 for a missing, invalid or expired token, 403 for a valid token without the needed permission, 400 for a malformed request.

Now the same bad token against a server with one check removed:

Check removed What gets through Careful server says
audience a token issued for another service works here 401
signature a forged token naming bob returned every user's note titles 401
issuer a token from another authorization server 401
scope a read-only token called the administrator's export 403

Warning: 'it decodes' is not 'it is valid'

The careless server in the signature row read the claims without verifying them. That mistake is easy to make: a JWT is readable by anyone, so decoding works for any token. The signature is the only thing that says the claims are true.

Audience, and why passthrough is forbidden

The audience check is the one people skip, because everything seems to work without it. The page names token passthrough as an anti-pattern: a server accepts tokens that were not issued to it and forwards them downstream. Measured: a token issued for https://calendar.example/api, presented at the notes server, was accepted (200, data returned) when the audience was not checked and refused (401) when it was.

The risks the page lists are worth knowing by name: security controls get bypassed (rate limits and checks that key on the audience); accountability is lost (the server cannot tell clients apart, and the downstream logs show the wrong caller); trust boundaries blur (a token accepted by many services is a master key); and the design is hard to evolve. The rule is simple: if the server calls an upstream API, it uses a separate token issued to the server by the upstream authorization server, and it never forwards the one the client gave it.