What you will be able to do
- Decide whether an MCP connection falls under the OAuth-based authorization spec or should take credentials from the environment
- Trace how an MCP client discovers its authorization server from a 401 challenge and chooses scopes
- Identify token-audience, token-leakage and PKCE gaps in an MCP authorization design
Key concept
Token audience binding — An access token for an MCP server must be requested for that specific server and accepted only by that server. The client names the intended server when it asks for the token, and the server rejects any token that was not issued for it.
1.First question: which transport is being secured?
Before you look for gaps in an MCP integration's authentication, find out which rulebook applies. The MCP authorization specification splits along transport lines. HTTP-based transports SHOULD conform to it. STDIO transports SHOULD NOT follow it, and instead retrieve credentials from the environment. Any other transport must follow the established security best practices for its own protocol.
That split produces two opposite design mistakes. The first is a remote server reached over HTTP with no OAuth flow in front of it, which leaves the requirements below unmet. The second is building an OAuth dance for a local STDIO server that the specification says should just read credentials from its environment. Where the specification does apply, the baseline is fixed: authorization servers must implement OAuth 2.1, with appropriate security measures for both confidential and public clients.
The STDIO server. The specification says STDIO implementations SHOULD NOT follow the authorization spec and should retrieve credentials from the environment instead. The remote HTTP server is the one that SHOULD conform to the OAuth-based flow.
Sources1
2.Discovery: from a 401 to the right authorization server
An MCP client does not come preconfigured with an authorization server. It discovers one. MCP servers must implement OAuth 2.0 Protected Resource Metadata (RFC9728), and clients must use that metadata to discover the authorization server. The authorization server, in turn, must offer OAuth 2.0 Authorization Server Metadata (RFC8414) or OpenID Connect Discovery 1.0. Because a server may offer either one, clients must support both.
The entry point is usually a 401. The server's WWW-Authenticate header points the client at the protected resource metadata document and can also name the scope the operation needs:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"Scope selection follows from the challenge. The client uses the scope parameter from the 401 if one is present. If it is not, the client falls back to every scope listed in scopes_supported in the resource metadata. That fallback is a least-privilege gap in its own right: a server that never names a scope in its challenges pushes clients toward requesting everything it supports.
Discovery also defends against a mixed-up issuer. The authorization response's iss parameter is checked against the issuer the client recorded, and whether it is present matters:
| authorization_response_iss_parameter_supported | iss in response | Client action |
|---|---|---|
| true | present | Compare to the recorded issuer using simple string comparison |
| true | absent | Reject the response |
| false or absent | present | Compare to the recorded issuer using simple string comparison |
| false or absent | absent | Proceed |
Sources1
3.Binding each token to one MCP server
The gap this section closes is token reuse across servers. A client connected to several MCP servers holds several tokens. If one server can accept a token that was minted for another, then compromising either one exposes both. The specification closes this from both sides.
On the client side, the resource parameter must be included in both authorization requests and token requests. It must identify the MCP server the token will be used with, using that server's canonical URI as defined in RFC 8707 Section 2. On the server side, MCP servers must validate that tokens presented to them were issued specifically for them. A review that finds only one half, such as clients sending resource while servers skip audience validation, has found a gap.
| Value | Valid canonical URI? |
|---|---|
| https://mcp.example.com/mcp | Yes |
| https://mcp.example.com:8443 | Yes |
| https://mcp.example.com/server/mcp | Yes, when the path is needed to identify an individual MCP server |
| mcp.example.com | No: missing scheme |
| https://mcp.example.com#fragment | No: contains fragment |
On trailing slashes: https://mcp.example.com/ and https://mcp.example.com are both valid absolute URIs. Implementations SHOULD still use the form without the slash, for interoperability, unless the slash is semantically significant.
While auditing an Agent SDK integration, you find allowed_tools=["Read", "Glob", "Grep", "Bash"] combined with permission_mode="bypassPermissions", and a canUseTool callback that logs every request and denies anything touching a credentials directory. The developer believed this callback was their safety net. What should the audit conclude?
Correct answer: A — The callback is shadowed: calls approved earlier in the order, including everything bypassPermissions approves, never reach canUseTool; a PreToolUse hook is needed if every call must be checked
- A. Correct. The SDK evaluates hooks, then deny rules, then ask rules, then permission mode, then allow rules, and only then calls canUseTool. With bypassPermissions set, the mode step approves every call that reaches it, so Bash and everything else never falls through to the callback. A logic check that must run on every call belongs in a PreToolUse hook instead, since hooks run first and a hook deny applies even in bypassPermissions mode.
- B. bypassPermissions is not scoped only to unlisted tools; it approves every call that reaches the permission-mode step, listed or not, so Bash calls are approved before the callback is ever consulted.
- C. This reverses the evaluation order. Hooks, deny rules, ask rules, and permission mode are all checked before allow rules and canUseTool, so canUseTool is one of the last steps, not the first.
- D. The requiresUserInteraction exception applies to AskUserQuestion and specially annotated MCP tools, but it does not change the outcome for the ordinary built-in tools in this scenario, which are fully shadowed by bypassPermissions regardless of that flag.
4.Where tokens travel and which flows to refuse
Once a token exists, the next gaps are about where it can leak. The client must send it in the Authorization request header, and access tokens must never go in the URI query string, where URLs end up in logs and history:
GET /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...The security considerations add hard lines on the flow itself. All authorization server endpoints must be served over HTTPS. Every redirect URI must be either localhost or HTTPS. Clients should warn about localhost-only redirect URIs and must clearly display the redirect URI hostname during authorization. PKCE is also non-negotiable. If an authorization server's metadata lacks code_challenge_methods_supported, the server does not support PKCE and the client must refuse to proceed. The same field must be checked in OpenID Connect provider metadata, even though that metadata format does not formally define it.
A security architect is designing an Agent SDK deployment that will process untrusted third-party documents and must prevent credential exfiltration even if a prompt injection convinces the agent to try to send data to an attacker-controlled endpoint. Which combination of controls together address this threat, given that TLS-encrypted traffic through a plain forward proxy cannot be inspected or have credentials injected into it? (Select 3)(Select 3)
Correct answers: A, B, C — Run the agent in a container with --network none and route all outbound calls through a Unix socket to a proxy on the host, so the agent has no direct network interface at all; Keep credentials outside the agent's security boundary entirely, injecting them at a proxy so the agent only ever sends requests without the underlying secret; Deploy a TLS-terminating proxy with its CA certificate trusted in the agent's environment so encrypted HTTPS traffic can be inspected and have credentials injected before re-encryption
- A. Correct. Removing the network namespace and forcing all traffic through a Unix socket to a host-side proxy means the agent has no path to reach arbitrary destinations directly, which is the architecture recommended for defense against prompt-injection-driven exfiltration.
- B. Correct. The proxy pattern keeps the actual credential outside the agent's trust boundary; the agent can make calls but never sees the secret, so even a fully compromised agent process can't leak the credential itself.
- C. Correct. A plain forward proxy only sees an opaque CONNECT tunnel for HTTPS and cannot modify or inspect its contents; injecting credentials into arbitrary HTTPS services requires terminating TLS at the proxy and having the agent trust its certificate.
- D. Not every tool respects HTTP_PROXY/HTTPS_PROXY, and even when they do, a standard forward proxy cannot see or modify HTTPS contents without terminating TLS, so it cannot inject credentials into an encrypted tunnel on its own.
- E. Granting broad IAM permissions directly to the agent process is the opposite of least privilege and increases the blast radius of a prompt injection rather than mitigating it.
- F. Relying on model judgment alone is not a security boundary; defense in depth requires network-level enforcement that holds even when the model's decision-making is manipulated.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Every MCP server, local STDIO ones included, should sit behind the OAuth 2.1 authorization flow.Why is that wrong?
The authorization spec targets HTTP-based transports. STDIO implementations should not follow it and should take credentials from the environment instead.
Covered in First question: which transport is being secured?
2.If the client sends the resource parameter, the server does not need to check the token's audience.Why is that wrong?
Both halves are required. The client names the target server, and the server must independently confirm that the token was issued for it.
Covered in Binding each token to one MCP server
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment.”
↩︎ First question: which transport is being secured?“Authorization servers MUST implement OAuth 2.1 with appropriate security measures for both confidential and public clients.”
↩︎ First question: which transport is being secured?“MCP clients MUST use OAuth 2.0 Protected Resource Metadata for authorization server discovery.”
↩︎ Discovery: from a 401 to the right authorization server“If scope is not available, use all scopes defined in scopes_supported from the Protected Resource Metadata document”
↩︎ Discovery: from a 401 to the right authorization server“true | absent | Reject the response”
↩︎ Discovery: from a 401 to the right authorization server“MUST use the canonical URI of the MCP server as defined in RFC 8707 Section 2.”
↩︎ Binding each token to one MCP server“MUST be included in both authorization requests and token requests.”
↩︎ Binding each token to one MCP server“Access tokens MUST NOT be included in the URI query string”
↩︎ Where tokens travel and which flows to refuse“Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment.”
↩︎ Exam trap 1 - 2.https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerationsOfficial docs
“MCP servers MUST validate that tokens presented to them were specifically issued for their use”
↩︎ Binding each token to one MCP server“If code_challenge_methods_supported is absent, the authorization server does not support PKCE and MCP clients MUST refuse to proceed.”
↩︎ Where tokens travel and which flows to refuse“All redirect URIs MUST be either localhost or use HTTPS.”
↩︎ Where tokens travel and which flows to refuse“MCP servers MUST validate that tokens presented to them were specifically issued for their use”
↩︎ Key concept“MCP servers MUST validate that tokens presented to them were specifically issued for their use”
↩︎ Exam trap 2