diff --git a/docs/servers/auth/authentication.mdx b/docs/servers/auth/authentication.mdx index 0c6ca4380..b00807eef 100644 --- a/docs/servers/auth/authentication.mdx +++ b/docs/servers/auth/authentication.mdx @@ -10,159 +10,184 @@ import { VersionBadge } from "/snippets/version-badge.mdx" - -Authentication is only relevant for FastMCP's HTTP-based transports (`http` and `sse`). STDIO transport relies on the security of the local environment where it runs. - +Authentication in MCP presents unique challenges that differ from traditional web applications. MCP clients need to discover authentication requirements automatically, negotiate OAuth flows without user intervention, and work seamlessly across different identity providers. FastMCP addresses these challenges by providing authentication patterns that integrate with the MCP protocol while remaining simple to implement and deploy. -FastMCP provides a powerful and flexible authentication system designed to fit modern application needs. Authentication is a fast-moving and often confusing part of the MCP specification, so FastMCP endeavors to make it as straightforward as possible while adhering to industry best practices as the MCP community evolves new standards. + +Authentication applies only to FastMCP's HTTP-based transports (`http` and `sse`). The STDIO transport inherits security from its local execution environment. + **Authentication is rapidly evolving in MCP.** The specification and best practices are changing quickly. FastMCP aims to provide stable, secure patterns that adapt to these changes while keeping your code simple and maintainable. -## Authentication Patterns +## MCP Authentication Challenges -MCP supports a variety of authentication options depending on how much of the authentication complexity you want to pull into your server itself. This can be thought of as a trade-off between whether your MCP server acts as a **Resource Server (RS)** that protects resources, an **Authorization Server (AS)** that handles user authentication and issues tokens, or neither. +Traditional web authentication assumes a human user with a browser who can interact with login forms and consent screens. MCP clients are often automated systems that need to authenticate without human intervention. This creates several unique requirements: -Think of it as a spectrum: +**Automatic Discovery**: MCP clients must discover authentication requirements by examining server metadata rather than encountering login redirects. -- **No responsibility:** Your server has no authentication *(none)* -- **Minimal responsibility:** Your server only validates tokens issued elsewhere *(RS)* -- **Moderate responsibility:** Your server coordinates with external identity providers *(RS + remote AS)* — **recommended for most users** -- **Full responsibility:** Your server handles the entire authentication lifecycle *(RS + AS)* +**Programmatic OAuth**: OAuth flows must work without human interaction, relying on pre-configured credentials or Dynamic Client Registration. -### Unauthenticated +**Token Management**: Clients need to obtain, refresh, and manage tokens automatically across multiple MCP servers. -Unauthenticated FastMCP servers run without any mechanisms to protect their components. All tools and resources are publicly accessible to any client that can connect to your server. +**Protocol Integration**: Authentication must integrate cleanly with MCP's transport mechanisms and error handling. -**Use this when:** -- Building development or testing environments -- Creating internal tools where network access controls provide sufficient security -- Prototyping before implementing proper authentication +These challenges mean that not all authentication approaches work well with MCP. The patterns that do work fall into three categories based on the level of authentication responsibility your server assumes. - +## Understanding Authentication Responsibility -**Security considerations:** -- Only suitable for trusted environments or carefully designed public APIs -- Consider network-level security (VPNs, firewalls, private networks) -- Exercise extreme caution when exposing unauthenticated servers to the public internet -- Ensure any public endpoints only expose non-sensitive data or operations - +Authentication responsibility exists on a spectrum. Your MCP server can validate tokens created elsewhere, coordinate with external identity providers, or handle the complete authentication lifecycle internally. Each approach involves different trade-offs between simplicity, security, and control. -### Token Verification +### Token Validation -Token verification is the conceptually simplest approach to authentication, where your FastMCP server acts as a pure **Resource Server**. Your server validates `Bearer` tokens on incoming requests but has no knowledge of how those tokens were obtained. This is analogous to how a web server validates API keys on incoming requests. Read more in the [token verification documentation](/servers/auth/token-verification). +Your server validates tokens but delegates their creation to external systems. This approach treats your MCP server as a pure resource server that trusts tokens signed by known issuers. - -**Protocol Note:** While simple to implement, this pattern operates somewhat outside the formal MCP authentication flow, which expects OAuth-style interactions. It's best suited for internal systems or when you have full control over token generation. - +Token validation works well when you already have authentication infrastructure that can issue structured tokens like JWTs. Your existing API gateway, microservices platform, or enterprise SSO system becomes the source of truth for user identity, while your MCP server focuses on its core functionality. -**Use this when:** -- You just need to validate tokens issued by another system -- Building internal microservices that trust a central auth service -- Working with static, long-lived API keys -- You control both the token issuer and your FastMCP server +The key insight is that token validation separates authentication (proving who you are) from authorization (determining what you can do). Your MCP server receives proof of identity in the form of a signed token and makes access decisions based on the claims within that token. -**Responsibilities you're taking on:** -- Token validation logic -- Ensuring tokens are securely transmitted to your server -- Managing token lifecycle in your issuing system +This pattern excels in microservices architectures where multiple services need to validate the same tokens, or when integrating MCP servers into existing systems that already handle user authentication. -### Remote OAuth +### External Identity Providers -This is the **recommended pattern for most FastMCP users** and follows the 2025-6-18 MCP protocol update. Your FastMCP server acts as a **Resource Server** and integrates with an external, trusted **Authorization Server** like WorkOS, Auth0, or Okta. You can learn more about this pattern in the [remote OAuth documentation](/servers/auth/remote-oauth). +Your server coordinates with established identity providers to create seamless authentication experiences for MCP clients. This approach leverages OAuth 2.0 and OpenID Connect protocols to delegate user authentication while maintaining control over authorization decisions. -**Use this when:** -- You want to integrate with external identity providers -- Building user-facing applications that need SSO -- You want enterprise-grade authentication without building it yourself -- You need features like multi-factor authentication, social logins, or directory sync +External identity providers handle the complex aspects of authentication: user credential verification, multi-factor authentication, account recovery, and security monitoring. Your MCP server receives tokens from these trusted providers and validates them using the provider's public keys. -**Responsibilities you're taking on:** -- Configuring your server to trust the external provider -- Token validation using the provider's public keys -- Mapping token claims to your application's user model +The MCP protocol's support for Dynamic Client Registration makes this pattern particularly powerful. MCP clients can automatically discover your authentication requirements and register themselves with your identity provider without manual configuration. -**What the external provider handles:** -- User login and consent flows -- Token issuance and management -- User account management -- Security features like MFA and fraud detection +This approach works best for production applications that need enterprise-grade authentication features without the complexity of building them from scratch. It scales well across multiple applications and provides consistent user experiences. -### Full OAuth Server +### Full OAuth Implementation - -**This is extremely advanced.** Most people should not build their own OAuth server. It requires deep security expertise and ongoing maintenance. Consider this only if you need complete control and have the resources to implement it securely. - +Your server implements a complete OAuth 2.0 authorization server, handling everything from user credential verification to token lifecycle management. This approach provides maximum control at the cost of significant complexity. -In this pattern, your FastMCP server acts as both the **Authorization Server** and **Resource Server**, handling the entire authentication lifecycle from user login to token validation. Read more in the [full OAuth server documentation](/servers/auth/full-oauth-server). +Full OAuth implementation means building user interfaces for login and consent, implementing secure credential storage, managing token lifecycles, and maintaining ongoing security updates. The complexity extends beyond initial implementation to include threat monitoring, compliance requirements, and keeping pace with evolving security best practices. -**Use this when:** -- Building a completely standalone application with its own user database -- You need full control over every aspect of authentication -- Prototyping OAuth flows locally without external dependencies -- You have the security expertise to implement OAuth securely +This pattern makes sense only when you need complete control over the authentication process, operate in air-gapped environments, or have specialized requirements that external providers cannot meet. -**Responsibilities you're taking on:** -- Secure user credential storage and verification -- OAuth flow implementation -- Token lifecycle management -- Security measures like rate limiting and attack prevention -- User consent and account management interfaces -- Ongoing security updates and compliance +## FastMCP Implementation -## Configuring Authentication +FastMCP translates these authentication responsibility levels into three concrete classes that handle the complexities of MCP protocol integration. -FastMCP provides a variety of `AuthProvider` classes that can be used to configure your server. To use one, instantiate it and pass it to your FastMCP server's `auth` parameter. +### TokenVerifier - +`TokenVerifier` provides pure token validation without OAuth metadata endpoints. This class focuses on the essential task of determining whether a token is valid and extracting authorization information from its claims. +The implementation handles JWT signature verification, expiration checking, and claim extraction. It validates tokens against known issuers and audiences, ensuring that tokens intended for your server are not accepted by other systems. -```python Token Verification +```python from fastmcp import FastMCP from fastmcp.server.auth.providers.jwt import JWTVerifier -jwt_verifier = JWTVerifier(...) +auth = JWTVerifier( + jwks_uri="https://your-auth-system.com/.well-known/jwks.json", + issuer="https://your-auth-system.com", + audience="your-mcp-server" +) -mcp = FastMCP(name="My Server", auth=jwt_verifier) +mcp = FastMCP(name="Protected Server", auth=auth) ``` -```python Remote OAuth +This example configures token validation against a JWT issuer. The `JWTVerifier` will fetch public keys from the JWKS endpoint and validate incoming tokens against those keys. Only tokens with the correct issuer and audience claims will be accepted. + +`TokenVerifier` works well when you control both the token issuer and your MCP server, or when integrating with existing JWT-based infrastructure. + +→ **Complete guide**: [Token Verification](/servers/auth/token-verification) + +### RemoteAuthProvider + +`RemoteAuthProvider` combines token validation with OAuth discovery metadata, enabling MCP clients to automatically discover and authenticate with external identity providers. + +This class extends `TokenVerifier` functionality by adding OAuth 2.0 protected resource endpoints that advertise your authentication requirements. MCP clients can examine these endpoints to understand which identity providers you trust and how to obtain valid tokens. + +The implementation handles the OAuth metadata generation required by the MCP specification while delegating actual token validation to an underlying `TokenVerifier`. This separation allows you to use different token validation strategies while maintaining consistent OAuth discovery behavior. + +```python from fastmcp import FastMCP from fastmcp.server.auth.providers.workos import AuthKitProvider -auth_provider = AuthKitProvider(...) +auth = AuthKitProvider( + authkit_domain="https://your-project.authkit.app", + base_url="https://your-fastmcp-server.com" +) -mcp = FastMCP(name="My Server", auth=auth_provider) +mcp = FastMCP(name="Enterprise Server", auth=auth) ``` - -### Environment Variables +This example uses WorkOS AuthKit as the external identity provider. The `AuthKitProvider` automatically configures token validation against WorkOS and provides the OAuth metadata that MCP clients need for automatic authentication. -For providers that support it, you can configure authentication entirely through environment variables. +`RemoteAuthProvider` excels for production applications that need professional identity management without implementation complexity. -There are two steps to this process: +→ **Complete guide**: [Remote OAuth](/servers/auth/remote-oauth) -1. Set `FASTMCP_SERVER_AUTH` to the registered name of your provider. For example, `JWT` for the `JWTVerifier` or `AUTHKIT` for the `AuthKitProvider`. -2. Set the appropriate environment variables for your provider in order to configure it. These are provider-specific and can be found in the provider's documentation. Not all providers will support environment variable configuration for all of their settings. +### OAuthProvider -For example, to configure a JWT verifier, you would set: +`OAuthProvider` implements a complete OAuth 2.0 authorization server within your MCP server. This class handles the full authentication lifecycle from user credential verification to token management. + +The implementation provides all required OAuth endpoints including authorization, token, and discovery endpoints. It manages client registration, user consent, and token lifecycle while integrating with your user storage and authentication logic. + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.oauth import MyOAuthProvider + +auth = MyOAuthProvider( + user_store=your_user_database, + client_store=your_client_registry, + # Additional configuration... +) + +mcp = FastMCP(name="Auth Server", auth=auth) +``` + +This example shows the basic structure of a custom OAuth provider. The actual implementation requires significant additional configuration for user management, client registration, and security policies. + +`OAuthProvider` should be used only when you have specific requirements that external providers cannot meet and the expertise to implement OAuth securely. + +→ **Complete guide**: [Full OAuth Server](/servers/auth/full-oauth-server) + +## Configuration Approaches + +FastMCP supports both programmatic configuration for maximum flexibility and environment-based configuration for deployment simplicity. + +### Programmatic Configuration + +Programmatic configuration provides complete control over authentication settings and allows for complex initialization logic. This approach works well during development and when you need to customize authentication behavior based on runtime conditions. + +Authentication providers are instantiated directly in your code with their required parameters. This makes dependencies explicit and allows your IDE to provide helpful autocompletion and type checking. + +### Environment Configuration + +Environment-based configuration separates authentication settings from application code, enabling the same codebase to work across different deployment environments without modification. + +FastMCP automatically detects authentication configuration from environment variables when no explicit `auth` parameter is provided. The configuration system supports all authentication providers and their various options. ```bash export FASTMCP_SERVER_AUTH=JWT -export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://your-idp.com/.well-known/jwks.json" +export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.example.com/jwks" +export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.example.com" +export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-server" ``` -And now your FastMCP server will automatically be configured with the JWT verifier: +With these environment variables set, creating an authenticated FastMCP server requires no additional configuration: ```python from fastmcp import FastMCP -# Assumes the environment variables are set as above -mcp = FastMCP(name="My Protected Server") - -assert mcp.auth is not None -assert mcp.auth.jwks_uri == "https://your-idp.com/.well-known/jwks.json" +# Authentication automatically configured from environment +mcp = FastMCP(name="My Server") ``` -Note that if you provide an `auth` parameter to your FastMCP server, it will override the environment variable configuration. You can also set `auth=None` to disable authentication entirely and prohibit environment variable configuration. \ No newline at end of file +This approach simplifies deployment pipelines and follows twelve-factor app principles for configuration management. + +## Choosing Your Implementation + +The authentication approach you choose depends on your existing infrastructure, security requirements, and operational constraints. + +**For most production applications, external identity providers offer the best balance of security, features, and simplicity.** This approach provides enterprise-grade authentication without implementation complexity and scales well as your application grows. The main trade-off is requiring users to sign up with your chosen identity provider, but this also brings benefits like professional user management, security monitoring, and compliance features. + +**Token validation works well when you already have authentication infrastructure that issues structured tokens.** If your organization already uses JWT-based systems, API gateways, or enterprise SSO that can generate tokens, this approach integrates seamlessly while keeping your MCP server focused on its core functionality. The simplicity comes from leveraging existing investment in authentication infrastructure. + +**Full OAuth implementation should be avoided unless you have compelling reasons that external providers cannot address.** Air-gapped environments, specialized compliance requirements, or unique organizational constraints might justify this approach, but it requires significant security expertise and ongoing maintenance commitment. The complexity extends far beyond initial implementation to include threat monitoring, security updates, and keeping pace with evolving attack vectors. + +FastMCP's architecture supports migration between these approaches as your requirements evolve. You can integrate with existing token systems initially and migrate to external identity providers as your application scales, or implement custom solutions when your requirements outgrow standard patterns. \ No newline at end of file diff --git a/docs/servers/auth/full-oauth-server.mdx b/docs/servers/auth/full-oauth-server.mdx index f04e055ac..da6b81519 100644 --- a/docs/servers/auth/full-oauth-server.mdx +++ b/docs/servers/auth/full-oauth-server.mdx @@ -11,119 +11,219 @@ import { VersionBadge } from "/snippets/version-badge.mdx" -**This is an extremely advanced pattern.** Building a secure, production-ready OAuth 2.1 server is a complex undertaking that requires deep expertise in authentication protocols, cryptography, and security best practices. +**This is an extremely advanced pattern that most users should avoid.** Building a secure OAuth 2.1 server requires deep expertise in authentication protocols, cryptography, and security best practices. The complexity extends far beyond initial implementation to include ongoing security monitoring, threat response, and compliance maintenance. -This pattern exists primarily to support the MCP protocol specification's requirements. **Most users should strongly prefer the [Remote OAuth pattern](/servers/auth/remote-oauth)** to integrate with a dedicated identity provider like WorkOS, Auth0, or Okta. +**Use [Remote OAuth](/servers/auth/remote-oauth) instead** unless you have compelling requirements that external identity providers cannot meet, such as air-gapped environments or specialized compliance needs. -In the **Full OAuth Server** pattern, your FastMCP server acts as both the **Authorization Server (AS)** and the **Resource Server (RS)**. It becomes responsible for the entire authentication lifecycle: +The Full OAuth Server pattern exists to support the MCP protocol specification's requirements. Your FastMCP server becomes both an Authorization Server and Resource Server, handling the complete authentication lifecycle from user login to token validation. -- **User Management**: Storing user credentials, profiles, and permissions -- **Client Registration**: Managing MCP client applications and their credentials -- **Authentication Flow**: Handling login pages, multi-factor authentication, and user consent -- **Token Lifecycle**: Issuing, refreshing, and revoking access tokens -- **Security Controls**: Rate limiting, audit logging, and threat detection +This documentation exists for completeness - the vast majority of applications should use external identity providers instead. -This pattern should only be considered if you have strict requirements that prevent using external identity providers, such as air-gapped environments or highly specialized compliance needs. +## OAuthProvider -## Building an OAuth Provider +FastMCP provides the `OAuthProvider` abstract class that implements the OAuth 2.1 specification. To use this pattern, you must subclass `OAuthProvider` and implement all required abstract methods. -To implement this pattern, you must subclass `fastmcp.server.auth.auth.OAuthProvider` and implement all of its abstract methods. This class extends the low-level OAuth authorization server interface and requires implementing the complete OAuth 2.1 specification. + +`OAuthProvider` handles OAuth endpoints, protocol flows, and security requirements, but delegates all storage, user management, and business logic to your implementation of the abstract methods. + -```python -from fastmcp.server.auth.auth import OAuthProvider -from mcp.server.auth.provider import ( - AccessToken, AuthorizationCode, RefreshToken, AuthorizationParams -) -from mcp.shared.auth import OAuthClientInformationFull, OAuthToken +## Required Implementation -class MyOAuthProvider(OAuthProvider): - """ - A production OAuth provider implementation. - - WARNING: This is a simplified example. A real implementation - requires extensive security considerations, persistent storage, - proper error handling, and adherence to OAuth 2.1 security - best practices. - """ - - def __init__(self, base_url: str): - super().__init__(base_url=base_url) - # Initialize your database connections, cryptographic keys, etc. - - # === Client Management === - async def get_client(self, client_id: str) -> OAuthClientInformationFull | None: - """Retrieve client information by ID from your database.""" - # Query your client database and return client info or None if not found - raise NotImplementedError - - async def register_client(self, client_info: OAuthClientInformationFull) -> None: - """Store new client registration information.""" - # Validate and save client metadata to your database - # May raise RegistrationError if client data is invalid - raise NotImplementedError - - # === Authorization Flow === - async def authorize( - self, client: OAuthClientInformationFull, params: AuthorizationParams - ) -> str: - """ - Handle authorization request and return redirect URL. - - Many implementations redirect to a third-party OAuth provider, creating - a chain: Client -> MCP Server -> External IdP -> MCP Server -> Client. - You must generate an authorization code with at least 128 bits of entropy. - """ - # Authenticate user, get consent, generate auth code, return redirect URL - raise NotImplementedError - - async def load_authorization_code( - self, client: OAuthClientInformationFull, authorization_code: str - ) -> AuthorizationCode | None: - """Load authorization code from storage by code string.""" - # Look up stored authorization code, return None if not found or expired - raise NotImplementedError - - # === Token Management === - async def exchange_authorization_code( - self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode - ) -> OAuthToken: - """Exchange authorization code for access and refresh tokens.""" - # Validate code, generate new token pair, invalidate the auth code - raise NotImplementedError - - async def load_refresh_token( - self, client: OAuthClientInformationFull, refresh_token: str - ) -> RefreshToken | None: - """Load refresh token from storage by token string.""" - # Look up refresh token, return None if not found or expired - raise NotImplementedError - - async def exchange_refresh_token( - self, - client: OAuthClientInformationFull, - refresh_token: RefreshToken, - scopes: list[str] - ) -> OAuthToken: - """Exchange refresh token for new access/refresh token pair.""" - # Should rotate both tokens for security best practices - raise NotImplementedError - - async def load_access_token(self, token: str) -> AccessToken | None: - """Load and validate access token - called on every protected request.""" - # Look up token, check expiration, return None if invalid - raise NotImplementedError - - async def revoke_token(self, token: AccessToken | RefreshToken) -> None: - """Revoke access or refresh token.""" - # Should revoke both access and refresh tokens regardless of which is provided - # Do nothing if token is already invalid or revoked - raise NotImplementedError - - # === Token Verification (AuthProvider interface) === - async def verify_token(self, token: str) -> AccessToken | None: - """Verify bearer token for incoming requests.""" - # This is called on every protected MCP request - # Typically delegates to load_access_token - return await self.load_access_token(token) -``` +You must implement these abstract methods to create a functioning OAuth server: + +### Client Management + + + + Retrieve client information by ID from your database. + + + + Client identifier to look up + + + + + + Client information object or `None` if client not found + + + + + + Store new client registration information in your database. + + + + Complete client registration information to store + + + + + + No return value + + + + + +### Authorization Flow + + + + Handle authorization request and return redirect URL. Must implement user authentication and consent collection. + + + + OAuth client making the authorization request + + + Authorization request parameters from the client + + + + + + Redirect URL to send the client to + + + + + + Load authorization code from storage by code string. Return `None` if code is invalid or expired. + + + + OAuth client attempting to use the authorization code + + + Authorization code string to look up + + + + + + Authorization code object or `None` if not found + + + + + +### Token Management + + + + Exchange authorization code for access and refresh tokens. Must validate code and create new tokens. + + + + OAuth client exchanging the authorization code + + + Valid authorization code object to exchange + + + + + + New OAuth token containing access and refresh tokens + + + + + + Load refresh token from storage by token string. Return `None` if token is invalid or expired. + + + + OAuth client attempting to use the refresh token + + + Refresh token string to look up + + + + + + Refresh token object or `None` if not found + + + + + + Exchange refresh token for new access/refresh token pair. Must validate scopes and token. + + + + OAuth client using the refresh token + + + Valid refresh token object to exchange + + + Requested scopes for the new access token + + + + + + New OAuth token with updated access and refresh tokens + + + + + + Load an access token by its token string. + + + + The access token to verify + + + + + + The access token object, or `None` if the token is invalid + + + + + + Revoke access or refresh token, marking it as invalid in storage. + + + + Token object to revoke and mark invalid + + + + + + No return value + + + + + + Verify bearer token for incoming requests. Return `AccessToken` if valid, `None` if invalid. + + + + Bearer token string from incoming request + + + + + + Access token object if valid, `None` if invalid or expired + + + + + +Each method must handle storage, validation, security, and error cases according to the OAuth 2.1 specification. The implementation complexity is substantial and requires expertise in OAuth security considerations. + + +**Security Notice:** OAuth server implementation involves numerous security considerations including PKCE, state parameters, redirect URI validation, token binding, replay attack prevention, and secure storage requirements. Mistakes can lead to serious security vulnerabilities. + \ No newline at end of file diff --git a/docs/servers/auth/remote-oauth.mdx b/docs/servers/auth/remote-oauth.mdx index bb258a77f..0e86b8974 100644 --- a/docs/servers/auth/remote-oauth.mdx +++ b/docs/servers/auth/remote-oauth.mdx @@ -1,7 +1,7 @@ --- title: Remote OAuth sidebarTitle: Remote OAuth -description: Integrate with external identity providers like WorkOS, Auth0, or Okta by trusting them to handle user authentication. +description: Integrate your FastMCP server with external identity providers like WorkOS, Auth0, and corporate SSO systems. icon: camera-cctv tag: NEW --- @@ -10,138 +10,180 @@ import { VersionBadge } from "/snippets/version-badge.mdx" -**Remote OAuth** is the recommended pattern for securing most production applications. In this model, your FastMCP server acts as a **Resource Server (RS)** and integrates with an external, trusted **Authorization Server (AS)**, such as WorkOS, Auth0, or a corporate SSO system. +Remote OAuth integration allows your FastMCP server to leverage external identity providers while maintaining the automated authentication flows that MCP clients require. This approach provides enterprise-grade authentication features without the complexity of implementing them yourself, making it the recommended pattern for most production applications. -This approach lets you leverage robust, feature-rich identity platforms for user management, multi-factor authentication, and social logins, while your FastMCP server focuses on its core job: providing tools and resources. + +Remote OAuth requires identity providers that support **Dynamic Client Registration (DCR)**. This enables MCP clients to automatically register and authenticate without manual configuration steps. + -### How It Works +## The Remote OAuth Challenge -The flow relies on the MCP client's ability to discover your server's authentication requirements. Your server doesn't handle logins itself; it tells the client where to find the real identity provider. +Traditional OAuth flows assume human users with web browsers who can interact with login forms, consent screens, and redirects. MCP clients operate differently - they're often automated systems that need to authenticate programmatically without human intervention. -The key endpoint is **`/.well-known/oauth-protected-resource`** which returns static metadata pointing to the authorization server. You can optionally also provide **`/.well-known/oauth-authorization-server`** that forwards the authorization server's metadata for convenience. +This creates several unique requirements that standard OAuth implementations don't address well: + +**Automatic Discovery**: MCP clients must discover authentication requirements by examining server metadata rather than encountering HTTP redirects. They need to know which identity provider to use and how to reach it before making any authenticated requests. + +**Programmatic Registration**: Clients need to register themselves with identity providers automatically. Manual client registration doesn't work when clients might be dynamically created tools or services. + +**Seamless Token Management**: Clients must obtain, store, and refresh tokens without user interaction. The authentication flow needs to work in headless environments where no human is available to complete OAuth consent flows. + +**Protocol Integration**: The authentication process must integrate cleanly with MCP's JSON-RPC transport layer and error handling mechanisms. + +These requirements mean that your MCP server needs to do more than just validate tokens - it needs to provide discovery metadata that enables MCP clients to understand and navigate your authentication requirements automatically. + +## MCP Authentication Discovery + +MCP authentication discovery relies on well-known endpoints that clients can examine to understand your authentication requirements. Your server becomes a bridge between MCP clients and your chosen identity provider. + +The core discovery endpoint is `/.well-known/oauth-protected-resource`, which tells clients that your server requires OAuth authentication and identifies the authorization servers you trust. This endpoint contains static metadata that points clients to your identity provider without requiring any dynamic lookups. ```mermaid sequenceDiagram participant Client - participant FastMCPServer as FastMCP (RS) - participant ExternalIdP as External IdP (AS) + participant FastMCPServer as FastMCP Server + participant ExternalIdP as Identity Provider Client->>FastMCPServer: 1. GET /.well-known/oauth-protected-resource FastMCPServer-->>Client: 2. "Use https://my-idp.com for auth" note over Client, ExternalIdP: Client goes directly to the IdP - Client->>ExternalIdP: 3. GET /.well-known/oauth-authorization-server - ExternalIdP-->>Client: 4. OAuth endpoints & capabilities + Client->>ExternalIdP: 3. Authenticate & get token via DCR + ExternalIdP-->>Client: 4. Access token - Client->>ExternalIdP: 5. User authenticates & gets token - ExternalIdP-->>Client: - - Client->>FastMCPServer: 6. MCP request with Bearer token - note right of FastMCPServer: Server verifies the token - FastMCPServer->>FastMCPServer: 7. Verify token signature
(using IdP's public keys) - FastMCPServer-->>Client: 8. MCP Response + Client->>FastMCPServer: 5. MCP request with Bearer token + FastMCPServer->>FastMCPServer: 6. Verify token signature + FastMCPServer-->>Client: 7. MCP response ``` -## Building a Custom Provider +This flow separates concerns cleanly: your MCP server handles resource protection and token validation, while your identity provider handles user authentication and token issuance. The client coordinates between these systems using standardized OAuth discovery mechanisms. -To connect to any identity provider, you create a custom `AuthProvider` subclass. This class has two main responsibilities: +## FastMCP Remote Authentication -1. **Verifying Tokens:** Validate tokens issued by the external provider. -2. **Forwarding Metadata:** Tell MCP clients where to find the external provider's login pages and token endpoints. + -### Step 1: Verifying Tokens +FastMCP provides `RemoteAuthProvider` to handle the complexities of remote OAuth integration. This class combines token validation capabilities with the OAuth discovery metadata that MCP clients require. -Your provider must implement the `verify_token` method. For most modern identity providers that issue JWTs, you can simply delegate this task to FastMCP's built-in `JWTVerifier`. +### RemoteAuthProvider + +`RemoteAuthProvider` works by composing a [`TokenVerifier`](/servers/auth/token-verification) with authorization server information. A `TokenVerifier` is another FastMCP authentication class that focuses solely on token validation - signature verification, expiration checking, and claim extraction. The `RemoteAuthProvider` takes that token validation capability and adds the OAuth discovery endpoints that enable MCP clients to automatically find and authenticate with your identity provider. + +This composition pattern means you can use any token validation strategy (JWT verification, introspection endpoints, custom validation logic) while maintaining consistent OAuth discovery behavior. The separation allows you to change token validation approaches without affecting the client discovery experience. + +The class automatically generates the required OAuth metadata endpoints using the MCP SDK's standardized route creation functions. This ensures compatibility with MCP clients while reducing the implementation complexity for server developers. + +### Basic Implementation + +Most applications can use `RemoteAuthProvider` directly without subclassing. The implementation requires a `TokenVerifier` instance, a list of trusted authorization servers, and your server's URL for metadata generation. ```python -from fastmcp.server.auth.auth import AuthProvider +from fastmcp import FastMCP +from fastmcp.server.auth import RemoteAuthProvider from fastmcp.server.auth.providers.jwt import JWTVerifier -from mcp.server.auth.provider import AccessToken +from pydantic import AnyHttpUrl -class MyIdPAuthProvider(AuthProvider): - def __init__(self): - super().__init__() - # The verifier validates tokens from the upstream provider. - self.token_verifier = JWTVerifier( - jwks_uri="https://my-idp.com/.well-known/jwks.json", - issuer="https://my-idp.com", - audience="my-fastmcp-api" - ) +# Configure token validation for your identity provider +token_verifier = JWTVerifier( + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" +) - async def verify_token(self, token: str) -> AccessToken | None: - return await self.token_verifier.verify_token(token) +# Create the remote auth provider +auth = RemoteAuthProvider( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")], + resource_server_url="https://api.yourcompany.com" +) + +mcp = FastMCP(name="Company API", auth=auth) ``` -### Step 2: Adding Discovery Metadata +This configuration creates a server that accepts tokens issued by `auth.yourcompany.com` and provides the OAuth discovery metadata that MCP clients need. The `JWTVerifier` handles token validation using your identity provider's public keys, while the `RemoteAuthProvider` generates the required OAuth endpoints. -Next, implement the `customize_auth_routes` method. The essential endpoint is `/.well-known/oauth-protected-resource` which tells clients where to find your authorization server. You can optionally add the authorization server forwarding endpoint for convenience. +The `authorization_servers` list tells MCP clients which identity providers you trust. The `resource_server_url` identifies your server in OAuth metadata, enabling proper token audience validation. + +### Custom Endpoints + +You can extend `RemoteAuthProvider` to add additional endpoints beyond the standard OAuth protected resource metadata. These don't have to be OAuth-specific - you can add any endpoints your authentication integration requires. ```python import httpx from starlette.responses import JSONResponse from starlette.routing import Route -class MyIdPAuthProvider(AuthProvider): - # ... (init and verify_token from above) ... +class CompanyAuthProvider(RemoteAuthProvider): + def __init__(self): + token_verifier = JWTVerifier( + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" + ) + + super().__init__( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")], + resource_server_url="https://api.yourcompany.com" + ) - def customize_auth_routes(self, routes: list[Route]) -> list[Route]: - # Essential: Tell clients which authorization server to use - async def protected_resource_metadata(request): - return JSONResponse({ - "resource": "https://my-fastmcp-server.com", - "authorization_servers": ["https://my-idp.com"], - "bearer_methods_supported": ["header"], - }) + def get_routes(self) -> list[Route]: + """Add custom endpoints to the standard protected resource routes.""" - routes.append(Route("/.well-known/oauth-protected-resource", protected_resource_metadata)) + # Get the standard OAuth protected resource routes + routes = super().get_routes() - # Optional: Forward the authorization server's metadata for convenience - # (Clients can also fetch this directly from the IdP) + # Add authorization server metadata forwarding for client convenience async def authorization_server_metadata(request): async with httpx.AsyncClient() as client: - resp = await client.get("https://my-idp.com/.well-known/oauth-authorization-server") - resp.raise_for_status() - return JSONResponse(resp.json()) - - routes.append(Route("/.well-known/oauth-authorization-server", authorization_server_metadata)) + response = await client.get( + "https://auth.yourcompany.com/.well-known/oauth-authorization-server" + ) + response.raise_for_status() + return JSONResponse(response.json()) + + routes.append( + Route("/.well-known/oauth-authorization-server", authorization_server_metadata) + ) + return routes + +mcp = FastMCP(name="Company API", auth=CompanyAuthProvider()) ``` -### Step 3: Using Your Provider +This pattern uses `super().get_routes()` to get the standard protected resource routes, then adds additional endpoints as needed. A common use case is providing authorization server metadata forwarding, which allows MCP clients to discover your identity provider's capabilities through your MCP server rather than contacting the identity provider directly. -With these two methods implemented, your auth provider is now fully integrated with your identity provider. You can now use your custom provider with FastMCP by passing it to the `auth` parameter of your `FastMCP` instance: +## WorkOS AuthKit Integration -```python -from fastmcp import FastMCP - -mcp = FastMCP(name="My Secure Server", auth=MyIdPAuthProvider()) -``` - - -## Example: WorkOS AuthKit Provider - -FastMCP provides a built-in provider for **WorkOS AuthKit** that handles this entire pattern for you. It's a perfect example of the remote OAuth pattern in action. - -**Prerequisites:** -1. A WorkOS account with an AuthKit project. -2. **Dynamic Client Registration (DCR)** must be enabled in your WorkOS application settings. -3. Your FastMCP server's URL must be added as a **Redirect URI** in your WorkOS project (can be localhost for development). +WorkOS AuthKit provides an excellent example of remote OAuth integration. The `AuthKitProvider` demonstrates how to implement both token validation and OAuth metadata forwarding in a production-ready package. ```python from fastmcp import FastMCP from fastmcp.server.auth.providers.workos import AuthKitProvider -# The AuthKitProvider implements both metadata forwarding and token validation. -auth_provider = AuthKitProvider( - # Your unique AuthKit domain from the WorkOS dashboard +auth = AuthKitProvider( authkit_domain="https://your-project.authkit.app", - # The URL of THIS FastMCP server (can be localhost for development) - base_url="https://your-fastmcp-server.com" + base_url="https://your-mcp-server.com" ) -mcp = FastMCP(name="My WorkOS-Protected Server", auth=auth_provider) +mcp = FastMCP(name="Protected Application", auth=auth) ``` - -For a complete, step-by-step tutorial on using this provider, see the [**WorkOS AuthKit Integration Guide**](/integrations/authkit). - \ No newline at end of file +The `AuthKitProvider` automatically configures JWT validation against WorkOS's public keys and provides both protected resource metadata and authorization server metadata forwarding. This implementation handles the complete remote OAuth integration with minimal configuration. + +WorkOS's support for Dynamic Client Registration makes it particularly well-suited for MCP applications. Clients can automatically register themselves with your WorkOS project and obtain the credentials needed for authentication without manual intervention. + +→ **Complete WorkOS tutorial**: [AuthKit Integration Guide](/integrations/authkit) + +## Implementation Considerations + +Remote OAuth integration requires careful attention to several technical details that affect reliability and security. + +**Token Validation Performance**: Your server validates every incoming token by checking signatures against your identity provider's public keys. Consider implementing key caching and rotation handling to minimize latency while maintaining security. + +**Error Handling**: Network issues with your identity provider can affect token validation. Implement appropriate timeouts, retry logic, and graceful degradation to maintain service availability during identity provider outages. + +**Audience Validation**: Ensure that tokens intended for your server are not accepted by other applications. Proper audience validation prevents token misuse across different services in your ecosystem. + +**Scope Management**: Map token scopes to your application's permission model consistently. Consider how scope changes affect existing tokens and plan for smooth permission updates. + +The complexity of these considerations reinforces why external identity providers are recommended over custom OAuth implementations. Established providers handle these technical details with extensive testing and operational experience. \ No newline at end of file diff --git a/docs/servers/auth/token-verification.mdx b/docs/servers/auth/token-verification.mdx index 0d5922c23..2c68f866d 100644 --- a/docs/servers/auth/token-verification.mdx +++ b/docs/servers/auth/token-verification.mdx @@ -1,7 +1,7 @@ --- title: Token Verification sidebarTitle: Token Verification -description: Protect your server by validating bearer tokens. +description: Protect your server by validating bearer tokens issued by external systems. icon: key tag: NEW --- @@ -10,202 +10,191 @@ import { VersionBadge } from "/snippets/version-badge.mdx" -In the **Token Verification** pattern, your FastMCP server acts as a pure **Resource Server**. It validates Bearer tokens on incoming requests and uses the token claims to authorize access to protected resources (tools, resources, and prompts). It does not participate in user login, token issuance, or consent flows - it trusts tokens issued by another system. This is equivalent to how a traditional web server validates API keys on incoming requests. +Token verification enables your FastMCP server to validate bearer tokens issued by external systems without participating in user authentication flows. Your server acts as a pure resource server, focusing on token validation and authorization decisions while delegating identity management to other systems in your infrastructure. -This is the right pattern if you have another system responsible for generating tokens and you simply need your FastMCP server to trust them or use the information in the token to make decisions. + +Token verification operates somewhat outside the formal MCP authentication flow, which expects OAuth-style discovery. It's best suited for internal systems, microservices architectures, or when you have full control over token generation and distribution. + -## JWT Verification +## Understanding Token Verification -JWT (JSON Web Token) verification is the recommended approach for production environments. The `JWTVerifier` class validates tokens using secure, asymmetric public key cryptography. This means your server only needs access to a public key to verify tokens, while the corresponding private key used for signing remains secure on your identity provider. +Token verification addresses scenarios where authentication responsibility is distributed across multiple systems. Your MCP server receives structured tokens containing identity and authorization information, validates their authenticity, and makes access control decisions based on their contents. -### Using the JWTVerifier +This pattern emerges naturally in microservices architectures where a central authentication service issues tokens that multiple downstream services validate independently. It also works well when integrating MCP servers into existing systems that already have established token-based authentication mechanisms. -The most common and flexible approach is to point the verifier at a **JSON Web Key Set (JWKS)** endpoint. This allows your identity provider to rotate signing keys automatically without requiring you to update your server's configuration. +### The Token Verification Model - -```python Using a JWKS Endpoint (Recommended) +Token verification treats your MCP server as a resource server in OAuth terminology. The key insight is that token validation and token issuance are separate concerns that can be handled by different systems. + +**Token Issuance**: Another system (API gateway, authentication service, or identity provider) handles user authentication and creates signed tokens containing identity and permission information. + +**Token Validation**: Your MCP server receives these tokens, verifies their authenticity using cryptographic signatures, and extracts authorization information from their claims. + +**Access Control**: Based on token contents, your server determines what resources, tools, and prompts the client can access. + +This separation allows your MCP server to focus on its core functionality while leveraging existing authentication infrastructure. The token acts as a portable proof of identity that travels with each request. + +### Token Security Considerations + +Token-based authentication relies on cryptographic signatures to ensure token integrity. Your MCP server validates tokens using public keys corresponding to the private keys used for token creation. This asymmetric approach means your server never needs access to signing secrets. + +Token validation must address several security requirements: signature verification ensures tokens haven't been tampered with, expiration checking prevents use of stale tokens, and audience validation ensures tokens intended for your server aren't accepted by other systems. + +The challenge in MCP environments is that clients need to obtain valid tokens before making requests, but the MCP protocol doesn't provide built-in discovery mechanisms for token endpoints. Clients must obtain tokens through separate channels or prior configuration. + +## FastMCP Token Verification + +FastMCP provides the `TokenVerifier` class to handle token validation complexity while remaining flexible about token sources and validation strategies. + +### TokenVerifier Design + +`TokenVerifier` focuses exclusively on token validation without providing OAuth discovery metadata. This makes it ideal for internal systems where clients already know how to obtain tokens, or for microservices that trust tokens from known issuers. + +The class validates token signatures, checks expiration timestamps, and extracts authorization information from token claims. It supports various token formats and validation strategies while maintaining a consistent interface for authorization decisions. + +You can subclass `TokenVerifier` to implement custom validation logic for specialized token formats or validation requirements. The base class handles common patterns while allowing extension for unique use cases. + +### JWT Token Verification + +JSON Web Tokens (JWTs) represent the most common token format for modern applications. FastMCP's `JWTVerifier` validates JWTs using industry-standard cryptographic techniques and claim validation. + +#### JWKS Endpoint Integration + +JWKS endpoint integration provides the most flexible approach for production systems. The verifier automatically fetches public keys from a JSON Web Key Set endpoint, enabling automatic key rotation without server configuration changes. + +```python from fastmcp import FastMCP from fastmcp.server.auth.providers.jwt import JWTVerifier -# The verifier will periodically fetch keys from this URL -# to validate incoming tokens. +# Configure JWT verification against your identity provider verifier = JWTVerifier( - jwks_uri="https://my-identity-provider.com/.well-known/jwks.json", - issuer="https://my-identity-provider.com/", - audience="my-mcp-server-identifier" + jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json", + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" ) -mcp = FastMCP(name="My Secure Server", auth=verifier) +mcp = FastMCP(name="Protected API", auth=verifier) ``` -```python Using a Static Public Key +This configuration creates a server that validates JWTs issued by `auth.yourcompany.com`. The verifier periodically fetches public keys from the JWKS endpoint and validates incoming tokens against those keys. Only tokens with the correct issuer and audience claims will be accepted. + +The `issuer` parameter ensures tokens come from your trusted authentication system, while `audience` validation prevents tokens intended for other services from being accepted by your MCP server. + +#### Static Public Key Verification + +Static public key verification works when you have a fixed signing key and don't need automatic key rotation. This approach simplifies deployment in environments where JWKS endpoints aren't available. + +```python from fastmcp import FastMCP from fastmcp.server.auth.providers.jwt import JWTVerifier -# This public key corresponds to the private key used by your token issuer. -# Use this only if a JWKS endpoint is not available. +# Use a static public key for token verification public_key_pem = """-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy... +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----""" verifier = JWTVerifier( public_key=public_key_pem, - issuer="https://my-identity-provider.com/", - audience="my-mcp-server-identifier" + issuer="https://auth.yourcompany.com", + audience="mcp-production-api" ) -mcp = FastMCP(name="My Secure Server", auth=verifier) -``` - - - - - - - PEM-encoded public key for JWT verification. Use this for static public key configuration. Cannot be used together with `jwks_uri`. - - - - URI to fetch JSON Web Key Set (JWKS) for automatic key rotation. Cannot be used together with `public_key`. - - - - Expected JWT issuer claim (`iss`) for validation. If provided, tokens must have a matching issuer. - - - - Expected JWT audience claim (`aud`) for validation. Can be a single string or list of accepted audiences. - - - - JWT signing algorithm (e.g., RS256, HS256, ES256, PS256) - - - - List of scopes that all tokens must have. Tokens missing any required scope will be rejected. - - - - Resource server URL for OAuth protocol compliance - - - - - -### Environment Variable Configuration - -You can configure the `JWTVerifier` entirely through environment variables. Set `FASTMCP_SERVER_AUTH=JWT` to enable automatic configuration, then provide the verifier's settings. - -Example configuration: -```bash -export FASTMCP_SERVER_AUTH=JWT -export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://your-idp.com/.well-known/jwks.json" -export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://your-idp.com" -export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="your-server-id" +mcp = FastMCP(name="Protected API", auth=verifier) ``` -Your FastMCP server will now be automatically configured with JWT verification: -```python -from fastmcp import FastMCP +This configuration validates tokens using a specific public key. The key must correspond to the private key used by your token issuer. While less flexible than JWKS endpoints, this approach works well for controlled environments or when using dedicated signing keys. -# This server is automatically protected with JWT verification -# based on the environment variables. -mcp = FastMCP(name="My Protected Server") -``` +### Development and Testing - - - PEM-encoded public key for JWT verification. Use this for static public key configuration. - +Development environments often need simpler token management without the complexity of full JWT infrastructure. FastMCP provides tools specifically designed for these scenarios. - - URI to fetch JSON Web Key Set (JWKS). Use this for automatic key rotation support. - +#### Static Token Verification - - Expected JWT issuer claim for validation - - - - Expected JWT audience claim for validation - - - - JWT signing algorithm (e.g., RS256, HS256, ES256) - - - - Comma-separated list of required scopes that all tokens must have - - - - Resource server URL for OAuth protocol compliance - - - -## Development Tools - -For local development and testing, managing JWTs can be cumbersome. FastMCP provides simpler tools for these scenarios. - -### Static Token Verification - -The `StaticTokenVerifier` validates tokens against a hardcoded set of tokens and claims. It's perfect for quickly getting a secure server running in your local environment for testing or prototyping without the complexity of a real identity provider. - - - -**Never use static token verification in production.** Tokens are stored as plain text strings, which is highly insecure. - - -To use the static token verifier, you need to provide a dictionary of tokens and their claims. Each key of the dictionary is a token, and the value is a dictionary of token data. Note that the tokens will be stored as plain text strings and validated as-is. For example, the following configuration would recognize a token provided in the `Authorization` header as `Bearer dev-token-for-alice` and load `alice@example.com` as the client ID: +Static token verification enables rapid development by accepting predefined tokens with associated claims. This approach eliminates the need for token generation infrastructure during development and testing. ```python from fastmcp import FastMCP from fastmcp.server.auth.providers.jwt import StaticTokenVerifier +# Define development tokens and their associated claims verifier = StaticTokenVerifier( tokens={ - "dev-token-for-alice": { - "client_id": "alice@example.com", - "scopes": ["read:data", "write:data"] + "dev-alice-token": { + "client_id": "alice@company.com", + "scopes": ["read:data", "write:data", "admin:users"] }, - "readonly-token-for-guest": { + "dev-guest-token": { "client_id": "guest-user", "scopes": ["read:data"] } }, - required_scopes=["read:data"] # Optionally enforce a base scope for all tokens. + required_scopes=["read:data"] ) mcp = FastMCP(name="Development Server", auth=verifier) ``` -### Generating Test Tokens +Clients can now authenticate using `Authorization: Bearer dev-alice-token` headers. The server will recognize the token and load the associated claims for authorization decisions. This approach enables immediate development without external dependencies. -To help test a `JWTVerifier`-protected server, FastMCP includes a simple `RSAKeyPair` utility to generate a key pair and sign your own JWTs. This is for convenience in development and is not intended for production use. + +Static token verification stores tokens as plain text and should never be used in production environments. It's designed exclusively for development and testing scenarios. + + +#### Test Token Generation + +Test token generation helps when you need to test JWT verification without setting up complete identity infrastructure. FastMCP includes utilities for generating test key pairs and signed tokens. + +```python +from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair + +# Generate a key pair for testing +key_pair = RSAKeyPair.generate() + +# Configure your server with the public key +verifier = JWTVerifier( + public_key=key_pair.public_key, + issuer="https://test.yourcompany.com", + audience="test-mcp-server" +) + +# Generate a test token using the private key +test_token = key_pair.create_token( + subject="test-user-123", + issuer="https://test.yourcompany.com", + audience="test-mcp-server", + scopes=["read", "write", "admin"] +) + +print(f"Test token: {test_token}") +``` + +This pattern enables comprehensive testing of JWT validation logic without depending on external token issuers. The generated tokens are cryptographically valid and will pass all standard JWT validation checks. + +## Environment Configuration + +FastMCP supports both programmatic and environment-based configuration for token verification, enabling flexible deployment across different environments. + +Environment-based configuration separates authentication settings from application code, following twelve-factor app principles and simplifying deployment pipelines. + +```bash +# Enable JWT verification +export FASTMCP_SERVER_AUTH=JWT + +# Configure JWT verification parameters +export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.company.com/.well-known/jwks.json" +export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.company.com" +export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-production-api" +export FASTMCP_SERVER_AUTH_JWT_REQUIRED_SCOPES="read:data,write:data" +``` + +With these environment variables configured, your FastMCP server automatically enables JWT verification: ```python from fastmcp import FastMCP -from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair -# 1. In a secure part of your test setup, generate a key pair. -key_pair = RSAKeyPair.generate() - -# 2. Configure your FastMCP server's verifier with the PUBLIC key. -auth_verifier = JWTVerifier( - public_key=key_pair.public_key, - issuer="https://dev.fastmcp.com", - audience="test-server" -) -mcp = FastMCP(name="Test Server", auth=auth_verifier) - -# 3. Use the PRIVATE key to create a valid token for your client tests. -test_token = key_pair.create_token( - subject="test-user-123", - issuer="https://dev.fastmcp.com", - audience="test-server", - scopes=["read", "write"] -) - -print(f"Generated Test Token:\n{test_token}") +# Authentication automatically configured from environment +mcp = FastMCP(name="Production API") ``` +This approach enables the same codebase to run across development, staging, and production environments with different authentication requirements. Development might use static tokens while production uses JWT verification, all controlled through environment configuration. + diff --git a/src/fastmcp/server/auth/__init__.py b/src/fastmcp/server/auth/__init__.py index a6da4b7be..c0b25e049 100644 --- a/src/fastmcp/server/auth/__init__.py +++ b/src/fastmcp/server/auth/__init__.py @@ -1,4 +1,4 @@ -from .auth import OAuthProvider, TokenVerifier +from .auth import OAuthProvider, TokenVerifier, RemoteAuthProvider from .providers.jwt import JWTVerifier, StaticTokenVerifier @@ -7,6 +7,7 @@ __all__ = [ "TokenVerifier", "JWTVerifier", "StaticTokenVerifier", + "RemoteAuthProvider", ] diff --git a/src/fastmcp/server/auth/auth.py b/src/fastmcp/server/auth/auth.py index 9fee9a2ee..d1e1271c5 100644 --- a/src/fastmcp/server/auth/auth.py +++ b/src/fastmcp/server/auth/auth.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING - from mcp.server.auth.provider import ( AccessToken, AuthorizationCode, @@ -11,6 +9,10 @@ from mcp.server.auth.provider import ( from mcp.server.auth.provider import ( TokenVerifier as TokenVerifierProtocol, ) +from mcp.server.auth.routes import ( + create_auth_routes, + create_protected_resource_routes, +) from mcp.server.auth.settings import ( ClientRegistrationOptions, RevocationOptions, @@ -18,11 +20,8 @@ from mcp.server.auth.settings import ( from pydantic import AnyHttpUrl from starlette.routing import Route -if TYPE_CHECKING: - pass - -class AuthProvider: +class AuthProvider(TokenVerifierProtocol): """Base class for all FastMCP authentication providers. This class provides a unified interface for all authentication providers, @@ -31,9 +30,18 @@ class AuthProvider: custom authentication routes. """ - def __init__(self, required_scopes: list[str] | None = None): - """Initialize the auth provider.""" - self.required_scopes: list[str] = required_scopes or [] + def __init__(self, resource_server_url: AnyHttpUrl | str | None = None): + """ + Initialize the auth provider. + + Args: + resource_server_url: The URL of this resource server. This is used + for RFC 8707 resource indicators, including creating the WWW-Authenticate + header. + """ + if isinstance(resource_server_url, str): + resource_server_url = AnyHttpUrl(resource_server_url) + self.resource_server_url = resource_server_url async def verify_token(self, token: str) -> AccessToken | None: """Verify a bearer token and return access info if valid. @@ -48,22 +56,34 @@ class AuthProvider: """ raise NotImplementedError("Subclasses must implement verify_token") - def customize_auth_routes(self, routes: list[Route]) -> list[Route]: - """Customize authentication routes after standard creation. + def get_routes(self) -> list[Route]: + """Get the routes for this authentication provider. - This method allows providers to modify or add to the standard OAuth routes. - The default implementation returns the routes unchanged. - - Args: - routes: List of standard routes (may be empty for token-only providers) + Each provider is responsible for creating whatever routes it needs: + - TokenVerifier: typically no routes (default implementation) + - RemoteAuthProvider: protected resource metadata routes + - OAuthProvider: full OAuth authorization server routes + - Custom providers: whatever routes they need Returns: - List of routes (potentially modified or extended) + List of routes for this provider """ - return routes + return [] + + def get_resource_metadata_url(self) -> AnyHttpUrl | None: + """Get the resource metadata URL for RFC 9728 compliance.""" + if self.resource_server_url is None: + return None + + # Add .well-known path for RFC 9728 compliance + resource_metadata_url = AnyHttpUrl( + str(self.resource_server_url).rstrip("/") + + "/.well-known/oauth-protected-resource" + ) + return resource_metadata_url -class TokenVerifier(AuthProvider, TokenVerifierProtocol): +class TokenVerifier(AuthProvider): """Base class for token verifiers (Resource Servers). This class provides token verification capability without OAuth server functionality. @@ -79,26 +99,71 @@ class TokenVerifier(AuthProvider, TokenVerifierProtocol): Initialize the token verifier. Args: - resource_server_url: The URL of this resource server (for RFC 8707 resource indicators) + resource_server_url: The URL of this resource server. This is used + for RFC 8707 resource indicators, including creating the WWW-Authenticate + header. required_scopes: Scopes that are required for all requests """ - # Initialize AuthProvider (no args needed) - AuthProvider.__init__(self, required_scopes=required_scopes) - - # Handle our own resource_server_url and required_scopes - self.resource_server_url: AnyHttpUrl | None - if resource_server_url is None: - self.resource_server_url = None - elif isinstance(resource_server_url, str): - self.resource_server_url = AnyHttpUrl(resource_server_url) - else: - self.resource_server_url = resource_server_url + super().__init__(resource_server_url=resource_server_url) + self.required_scopes = required_scopes or [] async def verify_token(self, token: str) -> AccessToken | None: """Verify a bearer token and return access info if valid.""" raise NotImplementedError("Subclasses must implement verify_token") +class RemoteAuthProvider(AuthProvider): + """Authentication provider for resource servers that verify tokens from known authorization servers. + + This provider composes a TokenVerifier with authorization server metadata to create + standardized OAuth 2.0 Protected Resource endpoints (RFC 9728). Perfect for: + - JWT verification with known issuers + - Remote token introspection services + - Any resource server that knows where its tokens come from + + Use this when you have token verification logic and want to advertise + the authorization servers that issue valid tokens. + """ + + def __init__( + self, + token_verifier: TokenVerifier, + authorization_servers: list[AnyHttpUrl], + resource_server_url: AnyHttpUrl | str, + ): + """Initialize the remote auth provider. + + Args: + token_verifier: TokenVerifier instance for token validation + authorization_servers: List of authorization servers that issue valid tokens + resource_server_url: URL of this resource server. This is used + for RFC 8707 resource indicators, including creating the WWW-Authenticate + header. + """ + super().__init__(resource_server_url=resource_server_url) + self.token_verifier = token_verifier + self.authorization_servers = authorization_servers + + async def verify_token(self, token: str) -> AccessToken | None: + """Verify token using the configured token verifier.""" + return await self.token_verifier.verify_token(token) + + def get_routes(self) -> list[Route]: + """Get OAuth routes for this provider. + + By default, returns only the standardized OAuth 2.0 Protected Resource routes. + Subclasses can override this method to add additional routes by calling + super().get_routes() and extending the returned list. + """ + assert self.resource_server_url is not None + + return create_protected_resource_routes( + resource_url=self.resource_server_url, + authorization_servers=self.authorization_servers, + scopes_supported=self.token_verifier.required_scopes, + ) + + class OAuthProvider( AuthProvider, OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken], @@ -181,17 +246,33 @@ class OAuthProvider( """ return await self.load_access_token(token) - def customize_auth_routes(self, routes: list[Route]) -> list[Route]: - """Customize OAuth authentication routes after standard creation. + def get_routes(self) -> list[Route]: + """Get OAuth authorization server routes and optional protected resource routes. - This method allows providers to modify the standard OAuth routes - returned by create_auth_routes. The default implementation returns - the routes unchanged. - - Args: - routes: List of standard OAuth routes from create_auth_routes + This method creates the full set of OAuth routes including: + - Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.) + - Optional protected resource routes if resource_server_url is configured Returns: - List of routes (potentially modified) + List of OAuth routes """ - return routes + + # Create standard OAuth authorization server routes + oauth_routes = create_auth_routes( + provider=self, + issuer_url=self.issuer_url, + service_documentation_url=self.service_documentation_url, + client_registration_options=self.client_registration_options, + revocation_options=self.revocation_options, + ) + + # Add protected resource routes if this server is also acting as a resource server + if self.resource_server_url: + protected_routes = create_protected_resource_routes( + resource_url=self.resource_server_url, + authorization_servers=[self.issuer_url], + scopes_supported=self.required_scopes, + ) + oauth_routes.extend(protected_routes) + + return oauth_routes diff --git a/src/fastmcp/server/auth/providers/jwt.py b/src/fastmcp/server/auth/providers/jwt.py index 30cfb1ded..0a213099b 100644 --- a/src/fastmcp/server/auth/providers/jwt.py +++ b/src/fastmcp/server/auth/providers/jwt.py @@ -16,7 +16,7 @@ from pydantic import AnyHttpUrl, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict from typing_extensions import TypedDict -from fastmcp.server.auth.auth import TokenVerifier +from fastmcp.server.auth import TokenVerifier from fastmcp.server.auth.registry import register_provider from fastmcp.utilities.logging import get_logger from fastmcp.utilities.types import NotSet, NotSetT diff --git a/src/fastmcp/server/auth/providers/workos.py b/src/fastmcp/server/auth/providers/workos.py index ff38b275c..21ad5f60c 100644 --- a/src/fastmcp/server/auth/providers/workos.py +++ b/src/fastmcp/server/auth/providers/workos.py @@ -1,15 +1,12 @@ from __future__ import annotations import httpx -from mcp.server.auth.provider import ( - AccessToken, -) from pydantic import AnyHttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict from starlette.responses import JSONResponse -from starlette.routing import BaseRoute, Route +from starlette.routing import Route -from fastmcp.server.auth.auth import AuthProvider, TokenVerifier +from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier from fastmcp.server.auth.providers.jwt import JWTVerifier from fastmcp.server.auth.registry import register_provider from fastmcp.utilities.logging import get_logger @@ -31,7 +28,7 @@ class AuthKitProviderSettings(BaseSettings): @register_provider("AUTHKIT") -class AuthKitProvider(AuthProvider): +class AuthKitProvider(RemoteAuthProvider): """AuthKit metadata provider for DCR (Dynamic Client Registration). This provider implements AuthKit integration using metadata forwarding @@ -83,8 +80,6 @@ class AuthKitProvider(AuthProvider): required_scopes: Optional list of scopes to require for all requests token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit """ - super().__init__() - settings = AuthKitProviderSettings.model_validate( { k: v @@ -109,19 +104,21 @@ class AuthKitProvider(AuthProvider): required_scopes=settings.required_scopes, ) - self.token_verifier = token_verifier + # Initialize RemoteAuthProvider with AuthKit as the authorization server + super().__init__( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl(self.authkit_domain)], + resource_server_url=self.base_url, + ) - async def verify_token(self, token: str) -> AccessToken | None: - """Verify an AuthKit token using the configured token verifier.""" - return await self.token_verifier.verify_token(token) + def get_routes(self) -> list[Route]: + """Get OAuth routes including AuthKit authorization server metadata forwarding. - def customize_auth_routes(self, routes: list[BaseRoute]) -> list[BaseRoute]: - """Add AuthKit metadata endpoints. - - This adds: - - /.well-known/oauth-authorization-server (forwards AuthKit metadata) - - /.well-known/oauth-protected-resource (returns FastMCP resource info) + This returns the standard protected resource routes plus an authorization server + metadata endpoint that forwards AuthKit's OAuth metadata to clients. """ + # Get the standard protected resource routes from RemoteAuthProvider + routes = super().get_routes() async def oauth_authorization_server_metadata(request): """Forward AuthKit OAuth authorization server metadata with FastMCP customizations.""" @@ -142,29 +139,13 @@ class AuthKitProvider(AuthProvider): status_code=500, ) - async def oauth_protected_resource_metadata(request): - """Return FastMCP resource server metadata.""" - return JSONResponse( - { - "resource": self.base_url, - "authorization_servers": [self.authkit_domain], - "bearer_methods_supported": ["header"], - } + # Add AuthKit authorization server metadata forwarding + routes.append( + Route( + "/.well-known/oauth-authorization-server", + endpoint=oauth_authorization_server_metadata, + methods=["GET"], ) - - routes.extend( - [ - Route( - "/.well-known/oauth-authorization-server", - endpoint=oauth_authorization_server_metadata, - methods=["GET"], - ), - Route( - "/.well-known/oauth-protected-resource", - endpoint=oauth_protected_resource_metadata, - methods=["GET"], - ), - ] ) return routes diff --git a/src/fastmcp/server/http.py b/src/fastmcp/server/http.py index 2192ac459..53c539d42 100644 --- a/src/fastmcp/server/http.py +++ b/src/fastmcp/server/http.py @@ -11,12 +11,10 @@ from mcp.server.auth.middleware.bearer_auth import ( RequireAuthMiddleware, ) from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol -from mcp.server.auth.routes import create_auth_routes from mcp.server.lowlevel.server import LifespanResultT from mcp.server.sse import SseServerTransport from mcp.server.streamable_http import EventStore from mcp.server.streamable_http_manager import StreamableHTTPSessionManager -from pydantic import AnyHttpUrl from starlette.applications import Starlette from starlette.middleware import Middleware from starlette.middleware.authentication import AuthenticationMiddleware @@ -25,7 +23,7 @@ from starlette.responses import Response from starlette.routing import BaseRoute, Mount, Route from starlette.types import Lifespan, Receive, Scope, Send -from fastmcp.server.auth.auth import AuthProvider, OAuthProvider, TokenVerifier +from fastmcp.server.auth.auth import AuthProvider from fastmcp.utilities.logging import get_logger if TYPE_CHECKING: @@ -71,51 +69,6 @@ class RequestContextMiddleware: await self.app(scope, receive, send) -def setup_auth_middleware_and_routes( - auth: AuthProvider, -) -> tuple[list[Middleware], list[Route], list[str]]: - """Set up authentication middleware and routes if auth is enabled. - - Args: - auth: An AuthProvider for authentication (TokenVerifier or OAuthProvider) - - Returns: - Tuple of (middleware, auth_routes, required_scopes) - """ - middleware: list[Middleware] = [ - Middleware( - AuthenticationMiddleware, - backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)), - ), - Middleware(AuthContextMiddleware), - ] - - auth_routes: list[Route] = [] - required_scopes: list[str] = auth.required_scopes or [] - - # Check if it's an OAuthProvider (has OAuth server capability) - if isinstance(auth, OAuthProvider): - # OAuthProvider: create standard OAuth routes first - standard_routes = list( - create_auth_routes( - provider=auth, - issuer_url=auth.issuer_url, - service_documentation_url=auth.service_documentation_url, - client_registration_options=auth.client_registration_options, - revocation_options=auth.revocation_options, - ) - ) - - # Allow provider to customize routes (e.g., for proxy behavior or metadata endpoints) - auth_routes = auth.customize_auth_routes(standard_routes) - else: - # Simple AuthProvider or TokenVerifier: start with empty routes - # Allow provider to add custom routes (e.g., metadata endpoints) - auth_routes = auth.customize_auth_routes([]) - - return middleware, auth_routes, required_scopes - - def create_base_app( routes: list[BaseRoute], middleware: list[Middleware], @@ -183,24 +136,27 @@ def create_sse_app( ) return Response() - # Get auth middleware and routes + # Set up auth if enabled if auth: - auth_middleware, auth_routes, required_scopes = ( - setup_auth_middleware_and_routes(auth) - ) + # Create auth middleware + auth_middleware = [ + Middleware( + AuthenticationMiddleware, + backend=BearerAuthBackend(auth), + ), + Middleware(AuthContextMiddleware), + ] + + # Get auth routes and scopes + auth_routes = auth.get_routes() + required_scopes = getattr(auth, "required_scopes", None) or [] + + # Get resource metadata URL for WWW-Authenticate header + resource_metadata_url = auth.get_resource_metadata_url() server_routes.extend(auth_routes) server_middleware.extend(auth_middleware) - # Determine resource_metadata_url for TokenVerifier - resource_metadata_url = None - if isinstance(auth, TokenVerifier) and auth.resource_server_url: - # Add .well-known path for RFC 9728 compliance - resource_metadata_url = AnyHttpUrl( - str(auth.resource_server_url).rstrip("/") - + "/.well-known/oauth-protected-resource" - ) - # Auth is enabled, wrap endpoints with RequireAuthMiddleware server_routes.append( Route( @@ -328,22 +284,25 @@ def create_streamable_http_app( # Add StreamableHTTP routes with or without auth if auth: - auth_middleware, auth_routes, required_scopes = ( - setup_auth_middleware_and_routes(auth) - ) + # Create auth middleware + auth_middleware = [ + Middleware( + AuthenticationMiddleware, + backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)), + ), + Middleware(AuthContextMiddleware), + ] + + # Get auth routes and scopes + auth_routes = auth.get_routes() + required_scopes = getattr(auth, "required_scopes", None) or [] + + # Get resource metadata URL for WWW-Authenticate header + resource_metadata_url = auth.get_resource_metadata_url() server_routes.extend(auth_routes) server_middleware.extend(auth_middleware) - # Determine resource_metadata_url for TokenVerifier - resource_metadata_url = None - if isinstance(auth, TokenVerifier) and auth.resource_server_url: - # Add .well-known path for RFC 9728 compliance - resource_metadata_url = AnyHttpUrl( - str(auth.resource_server_url).rstrip("/") - + "/.well-known/oauth-protected-resource" - ) - # Auth is enabled, wrap endpoint with RequireAuthMiddleware server_routes.append( Mount( diff --git a/tests/server/http/test_auth_setup.py b/tests/server/http/test_auth_setup.py deleted file mode 100644 index 695f05423..000000000 --- a/tests/server/http/test_auth_setup.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Tests for authentication setup in HTTP apps.""" - -import pytest -from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend -from mcp.server.auth.provider import AccessToken -from starlette.middleware import Middleware -from starlette.middleware.authentication import AuthenticationMiddleware - -from fastmcp.server.auth.providers.in_memory import InMemoryOAuthProvider -from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair -from fastmcp.server.http import setup_auth_middleware_and_routes - - -class TestSetupAuthMiddlewareAndRoutes: - """Test setup_auth_middleware_and_routes with TokenVerifier providers.""" - - @pytest.fixture - def jwt_verifier(self) -> JWTVerifier: - """Create JWTVerifier for testing.""" - key_pair = RSAKeyPair.generate() - return JWTVerifier( - public_key=key_pair.public_key, - issuer="https://test.example.com", - audience="https://api.example.com", - required_scopes=["read", "write"], - ) - - @pytest.fixture - def in_memory_provider(self) -> InMemoryOAuthProvider: - """Create InMemoryOAuthProvider for testing.""" - return InMemoryOAuthProvider( - base_url="https://test.example.com", - required_scopes=["user"], - ) - - def test_setup_with_jwt_verifier(self, jwt_verifier: JWTVerifier): - """Test that setup works with JWTVerifier as TokenVerifier.""" - middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes( - jwt_verifier - ) - - # Should return middleware list - assert isinstance(middleware, list) - assert len(middleware) == 2 # AuthenticationMiddleware + AuthContextMiddleware - - # First middleware should be AuthenticationMiddleware with BearerAuthBackend - auth_middleware = middleware[0] - assert isinstance(auth_middleware, Middleware) - assert auth_middleware.cls == AuthenticationMiddleware - assert "backend" in auth_middleware.kwargs - - backend = auth_middleware.kwargs["backend"] - assert isinstance(backend, BearerAuthBackend) - assert backend.token_verifier is jwt_verifier # type: ignore[attr-defined] - - # Should return auth routes - assert isinstance(auth_routes, list) - assert len(auth_routes) == 0 # TokenVerifier should not have OAuth routes - - # Should return required scopes - assert required_scopes == ["read", "write"] - - def test_setup_with_in_memory_provider( - self, in_memory_provider: InMemoryOAuthProvider - ): - """Test that setup works with InMemoryOAuthProvider as TokenVerifier.""" - middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes( - in_memory_provider - ) - - # Should return middleware list - assert isinstance(middleware, list) - assert len(middleware) == 2 - - # Backend should use the provider as token verifier - auth_middleware = middleware[0] - backend = auth_middleware.kwargs["backend"] - assert isinstance(backend, BearerAuthBackend) - assert backend.token_verifier is in_memory_provider # type: ignore[attr-defined] - - # Should return required scopes - assert required_scopes == ["user"] - - def test_setup_preserves_provider_functionality(self, jwt_verifier: JWTVerifier): - """Test that setup doesn't break the provider's functionality.""" - # Setup should not modify the provider - original_issuer = jwt_verifier.issuer - original_scopes = jwt_verifier.required_scopes - - middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes( - jwt_verifier - ) - - # Provider should be unchanged - assert jwt_verifier.issuer == original_issuer - assert jwt_verifier.required_scopes == original_scopes - - # Provider should still work as TokenVerifier - assert hasattr(jwt_verifier, "verify_token") - assert callable(jwt_verifier.verify_token) - - -class MockOAuthProvider: - """Mock OAuth provider that implements TokenVerifier.""" - - def __init__(self, required_scopes=None, issuer_url="http://localhost:8000"): - from pydantic import AnyHttpUrl - - from fastmcp.server.auth.auth import ( - ClientRegistrationOptions, - RevocationOptions, - ) - - self.required_scopes = required_scopes or [] - self.issuer_url = AnyHttpUrl(issuer_url) - self.service_documentation_url = None - self.client_registration_options = ClientRegistrationOptions(enabled=False) - self.revocation_options = RevocationOptions(enabled=False) - - async def verify_token(self, token: str) -> AccessToken | None: - """Mock verify_token implementation.""" - if token == "valid-token": - return AccessToken( - token=token, - client_id="mock-client", - scopes=self.required_scopes, - expires_at=None, - ) - return None - - def customize_auth_routes(self, routes): - """Mock customize_auth_routes implementation.""" - return routes - - -class TestSetupWithMockProvider: - """Test setup function with mock provider.""" - - def test_setup_with_mock_token_verifier(self): - """Test that setup works with any TokenVerifier implementation.""" - mock_provider = MockOAuthProvider(required_scopes=["mock-scope"]) - - middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes( - mock_provider # type: ignore[arg-type] - ) - - # Should work with any TokenVerifier - assert len(middleware) == 2 - auth_middleware = middleware[0] - backend = auth_middleware.kwargs["backend"] - assert isinstance(backend, BearerAuthBackend) - assert backend.token_verifier is mock_provider # type: ignore[attr-defined] - - assert required_scopes == ["mock-scope"] - - async def test_setup_middleware_can_authenticate(self): - """Test that the setup middleware can actually authenticate requests.""" - mock_provider = MockOAuthProvider() - - middleware, _, _ = setup_auth_middleware_and_routes(mock_provider) # type: ignore[arg-type] - - # Extract the BearerAuthBackend - auth_middleware = middleware[0] - backend = auth_middleware.kwargs["backend"] - - # Test authentication with valid token - from starlette.requests import HTTPConnection - - scope = { - "type": "http", - "headers": [(b"authorization", b"Bearer valid-token")], - } - conn = HTTPConnection(scope) - - result = await backend.authenticate(conn) # type: ignore[attr-defined] - assert result is not None - - credentials, user = result - assert user.username == "mock-client" - - # Test authentication with invalid token - scope = { - "type": "http", - "headers": [(b"authorization", b"Bearer invalid-token")], - } - conn = HTTPConnection(scope) - - result = await backend.authenticate(conn) # type: ignore[attr-defined] - assert result is None