
Authentication and Authorization for MCP Servers
A guide to implementing OAuth, scopes, and confused deputy prevention in MCP servers for secure agent access.
The Model Context Protocol (MCP) enables AI agents to interact with external data sources and tools. While the specification defines how these interactions occur, it deliberately leaves security implementation to the server side. For backend developers and security engineers, this means the MCP server is not just a data provider; it is a security boundary. If you treat an MCP server as a simple API endpoint, you inherit the risks of unauthenticated access or privilege escalation. This guide explains how to secure MCP servers using standard OAuth 2.1 flows, fine-grained scopes, and specific mechanisms to prevent confused deputy attacks.
The Trust Boundary: Who is Asking?
In an MCP interaction, there are three primary actors: the User, the Client (or Agent orchestrator), and the MCP Server. The User owns the data. The Client requests tools from the Server. The Server executes the tools.
Without authentication, the MCP Server cannot distinguish between a request from a trusted internal service and a request from a malicious actor. It also cannot determine if the Client has permission to access specific data. Therefore, every MCP request must carry an identity assertion.
OAuth 2.1 is the recommended mechanism for this. The Client presents an Access Token to the MCP Server. This token proves two things:
- Authentication: Who is making the request? (The
subclaim). - Authorization: What is this token allowed to do? (The
scopeclaim).
The aud claim is also critical, but it serves to validate the recipient of the token, ensuring it was issued specifically for this server, rather than defining permissions. If your MCP server does not validate the token signature, issuer, and audience, it is effectively open to the public internet.
Scopes as Policy Boundaries
Scopes are not just metadata; they are the primary authorization policy for MCP servers. An MCP server exposes multiple tools (e.g., read_database, write_logs, delete_user). Each tool should map to one or more scopes.
When a Client requests a tool, it must present a token that includes the corresponding scope. The MCP server validates this before executing any logic.
Consider a server with two tools:
get_user_profile: Requires scopeprofile:readupdate_user_profile: Requires scopeprofile:write
A token issued with only profile:read can call get_user_profile but will be rejected by update_user_profile. This is basic scope-based access control, but it is critical for MCP because agents often have broad permissions by default. By enforcing strict scope-to-tool mapping, you limit the blast radius of a compromised token. This approach aligns with broader api-security best practices, ensuring that access control is granular and explicit.
# Pseudocode for MCP Server Tool Execution
def execute_tool(tool_name, access_token):
required_scope = TOOL_SCOPE_MAP[tool_name]
if not access_token.has_scope(required_scope):
raise AuthorizationError(
f"Missing scope '{required_scope}' for tool '{tool_name}'"
)
# Proceed with execution
return run_tool_logic(tool_name)The Confused Deputy Problem
The most sophisticated threat to MCP servers is the confused deputy problem. This occurs when a trusted entity (the Client/Agent) is tricked into performing an action on behalf of another entity, or using credentials for a purpose they were not intended for.
In the MCP context, this happens when an Agent holds a valid token for accessing a specific resource but attempts to use it to access a different resource or perform a different action.
Example Scenario
Imagine an Agent is authorized to read data from Database A via the MCP Server. The token has scope db:a:read. However, the MCP Server also exposes a tool export_all_data that can export data from multiple databases. If the MCP Server does not validate that the token’s scope specifically authorizes export_all_data for the target resource, the Agent could potentially export data from Database B if the tool’s implementation allows it, even though the token only granted access to Database A.
This is a confused deputy attack: the MCP Server trusts the token’s signature, but fails to validate the context of the request against the token’s permissions. Preventing such issues is central to robust api-security, as it ensures that tokens cannot be repurposed across different resources or contexts.
Mitigation: Audience and Resource Binding
To prevent this, MCP servers must enforce two mechanisms:
-
Audience Validation (
audclaim): The token must explicitly list the MCP Server’s identifier as its audience. This ensures the token was issued for this server and not for some other service. If theaudclaim does not match the MCP Server’s expected identifier, reject the token immediately. -
Resource-Scoped Scopes: Scopes should be bound to specific resources. Instead of a generic
data:read, use scopes likedata:database_a:read. When the Agent calls a tool, the MCP Server checks if the token’s scopes cover the specific resource being accessed.
# Validating Audience and Resource Scope
def validate_token(token, target_resource):
# 1. Check Audience
if 'mcp-server-id' not in token.aud:
raise SecurityError("Token not intended for this server")
# 2. Check Resource-Specific Scope
required_scope = f"data:{target_resource}:read"
if required_scope not in token.scopes:
raise AuthorizationError(f"Access denied to resource {target_resource}")
return TrueImplementation Patterns
Implementing secure MCP servers requires integrating with an OAuth 2.0 Authorization Server. The OAuth 2.1 specification is backward compatible with existing 2.0 servers, allowing you to adopt the stricter security posture of 2.1 while leveraging established infrastructure. The flow typically involves:
- Discovery: The MCP Client fetches the Authorization Server’s OpenID Connect discovery document from the
.well-known/openid-configurationendpoint, which specifies the JWKS (JSON Web Key Set) URI used for signature verification. - Token Validation: The MCP Server verifies the token’s signature using the JWKS.
- Claim Extraction: Extract
sub(user),aud(server), andscope(permissions). - Policy Enforcement: Map scopes to MCP tools and resources.
Recommended Library Usage
Use established JWT libraries rather than rolling your own. For example, in Python, PyJWT or authlib can handle JWKS fetching and verification. In Node.js, jose or passport-jwt are standard choices.
// Node.js Example using jose
import { jwtVerify } from 'jose';
async function verifyMcpToken(token, jwksUrl) {
const jwks = await getJwks(jwksUrl);
const { payload } = await jwtVerify(token, jwks, {
audience: 'mcp-server-id', // Enforce audience
issuer: 'https://auth.example.com'
});
return payload;
}Conclusion
Securing an MCP server is not optional; it is foundational. By implementing OAuth 2.1 flows, enforcing strict scope-to-tool mappings, and validating the aud claim to prevent confused deputy attacks, you ensure that your MCP server remains a trusted, secure gateway for AI agents. Always assume the Agent is untrusted until proven otherwise by the token’s claims.
Related posts
GraphQL Security: Authentication and Authorization Patterns
An examination of GraphQL security patterns for authentication and authorization, including query complexity limits and API security best practices.
How to Revoke a JWT: Denylist & Expiry Strategies
Learn how to revoke a JWT you already issued using denylists and token invalidation strategies for secure backend authentication.
WebSocket Authentication: OAuth, JWT, or Something Else
Explore WebSocket authentication strategies including OAuth, JWT, and handshake protocols to secure real-time connections effectively.