Spaces:
Running
Running
Jeremiah Lowin commited on
Introduce `RemoteAuthProvider` for cleaner external identity provider integration, update docs (#1346)
Browse files- docs/servers/auth/authentication.mdx +119 -94
- docs/servers/auth/full-oauth-server.mdx +213 -113
- docs/servers/auth/remote-oauth.mdx +122 -80
- docs/servers/auth/token-verification.mdx +126 -137
- src/fastmcp/server/auth/__init__.py +2 -1
- src/fastmcp/server/auth/auth.py +122 -41
- src/fastmcp/server/auth/providers/jwt.py +1 -1
- src/fastmcp/server/auth/providers/workos.py +21 -40
- src/fastmcp/server/http.py +32 -73
- tests/server/http/test_auth_setup.py +0 -189
docs/servers/auth/authentication.mdx
CHANGED
|
@@ -10,159 +10,184 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
|
|
|
|
|
|
| 13 |
<Tip>
|
| 14 |
-
Authentication
|
| 15 |
</Tip>
|
| 16 |
|
| 17 |
-
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.
|
| 18 |
-
|
| 19 |
<Warning>
|
| 20 |
**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.
|
| 21 |
</Warning>
|
| 22 |
|
| 23 |
-
## Authentication
|
| 24 |
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
|
| 29 |
-
|
| 30 |
-
- **Minimal responsibility:** Your server only validates tokens issued elsewhere *(RS)*
|
| 31 |
-
- **Moderate responsibility:** Your server coordinates with external identity providers *(RS + remote AS)* — **recommended for most users**
|
| 32 |
-
- **Full responsibility:** Your server handles the entire authentication lifecycle *(RS + AS)*
|
| 33 |
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
-
|
| 39 |
-
- Building development or testing environments
|
| 40 |
-
- Creating internal tools where network access controls provide sufficient security
|
| 41 |
-
- Prototyping before implementing proper authentication
|
| 42 |
|
| 43 |
-
|
| 44 |
|
| 45 |
-
|
| 46 |
-
- Only suitable for trusted environments or carefully designed public APIs
|
| 47 |
-
- Consider network-level security (VPNs, firewalls, private networks)
|
| 48 |
-
- Exercise extreme caution when exposing unauthenticated servers to the public internet
|
| 49 |
-
- Ensure any public endpoints only expose non-sensitive data or operations
|
| 50 |
-
</Warning>
|
| 51 |
|
| 52 |
-
### Token
|
| 53 |
|
| 54 |
-
|
| 55 |
|
| 56 |
-
|
| 57 |
-
**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.
|
| 58 |
-
</Note>
|
| 59 |
|
| 60 |
-
|
| 61 |
-
- You just need to validate tokens issued by another system
|
| 62 |
-
- Building internal microservices that trust a central auth service
|
| 63 |
-
- Working with static, long-lived API keys
|
| 64 |
-
- You control both the token issuer and your FastMCP server
|
| 65 |
|
| 66 |
-
|
| 67 |
-
- Token validation logic
|
| 68 |
-
- Ensuring tokens are securely transmitted to your server
|
| 69 |
-
- Managing token lifecycle in your issuing system
|
| 70 |
|
| 71 |
-
###
|
| 72 |
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
-
- You want to integrate with external identity providers
|
| 77 |
-
- Building user-facing applications that need SSO
|
| 78 |
-
- You want enterprise-grade authentication without building it yourself
|
| 79 |
-
- You need features like multi-factor authentication, social logins, or directory sync
|
| 80 |
|
| 81 |
-
|
| 82 |
-
- Configuring your server to trust the external provider
|
| 83 |
-
- Token validation using the provider's public keys
|
| 84 |
-
- Mapping token claims to your application's user model
|
| 85 |
|
| 86 |
-
|
| 87 |
-
- User login and consent flows
|
| 88 |
-
- Token issuance and management
|
| 89 |
-
- User account management
|
| 90 |
-
- Security features like MFA and fraud detection
|
| 91 |
|
| 92 |
-
### Full OAuth
|
| 93 |
|
| 94 |
-
|
| 95 |
-
**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.
|
| 96 |
-
</Warning>
|
| 97 |
|
| 98 |
-
|
| 99 |
|
| 100 |
-
|
| 101 |
-
- Building a completely standalone application with its own user database
|
| 102 |
-
- You need full control over every aspect of authentication
|
| 103 |
-
- Prototyping OAuth flows locally without external dependencies
|
| 104 |
-
- You have the security expertise to implement OAuth securely
|
| 105 |
|
| 106 |
-
|
| 107 |
-
- Secure user credential storage and verification
|
| 108 |
-
- OAuth flow implementation
|
| 109 |
-
- Token lifecycle management
|
| 110 |
-
- Security measures like rate limiting and attack prevention
|
| 111 |
-
- User consent and account management interfaces
|
| 112 |
-
- Ongoing security updates and compliance
|
| 113 |
|
| 114 |
-
|
| 115 |
|
| 116 |
-
|
| 117 |
|
| 118 |
-
|
| 119 |
|
|
|
|
| 120 |
|
| 121 |
-
```python
|
| 122 |
from fastmcp import FastMCP
|
| 123 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 124 |
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
mcp = FastMCP(name="
|
| 128 |
```
|
| 129 |
|
| 130 |
-
``
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
from fastmcp import FastMCP
|
| 132 |
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
| 133 |
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
```
|
| 138 |
-
</CodeGroup>
|
| 139 |
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
|
| 143 |
|
| 144 |
-
|
| 145 |
|
| 146 |
-
|
| 147 |
-
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.
|
| 148 |
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
```bash
|
| 152 |
export FASTMCP_SERVER_AUTH=JWT
|
| 153 |
-
export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://
|
|
|
|
|
|
|
| 154 |
```
|
| 155 |
|
| 156 |
-
|
| 157 |
|
| 158 |
```python
|
| 159 |
from fastmcp import FastMCP
|
| 160 |
|
| 161 |
-
#
|
| 162 |
-
mcp = FastMCP(name="My
|
| 163 |
-
|
| 164 |
-
assert mcp.auth is not None
|
| 165 |
-
assert mcp.auth.jwks_uri == "https://your-idp.com/.well-known/jwks.json"
|
| 166 |
```
|
| 167 |
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
+
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.
|
| 14 |
+
|
| 15 |
<Tip>
|
| 16 |
+
Authentication applies only to FastMCP's HTTP-based transports (`http` and `sse`). The STDIO transport inherits security from its local execution environment.
|
| 17 |
</Tip>
|
| 18 |
|
|
|
|
|
|
|
| 19 |
<Warning>
|
| 20 |
**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.
|
| 21 |
</Warning>
|
| 22 |
|
| 23 |
+
## MCP Authentication Challenges
|
| 24 |
|
| 25 |
+
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:
|
| 26 |
|
| 27 |
+
**Automatic Discovery**: MCP clients must discover authentication requirements by examining server metadata rather than encountering login redirects.
|
| 28 |
|
| 29 |
+
**Programmatic OAuth**: OAuth flows must work without human interaction, relying on pre-configured credentials or Dynamic Client Registration.
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
**Token Management**: Clients need to obtain, refresh, and manage tokens automatically across multiple MCP servers.
|
| 32 |
|
| 33 |
+
**Protocol Integration**: Authentication must integrate cleanly with MCP's transport mechanisms and error handling.
|
| 34 |
|
| 35 |
+
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.
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
## Understanding Authentication Responsibility
|
| 38 |
|
| 39 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
### Token Validation
|
| 42 |
|
| 43 |
+
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.
|
| 44 |
|
| 45 |
+
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.
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
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.
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
### External Identity Providers
|
| 52 |
|
| 53 |
+
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.
|
| 54 |
|
| 55 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
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.
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
### Full OAuth Implementation
|
| 62 |
|
| 63 |
+
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.
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
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.
|
| 66 |
|
| 67 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
## FastMCP Implementation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
FastMCP translates these authentication responsibility levels into three concrete classes that handle the complexities of MCP protocol integration.
|
| 72 |
|
| 73 |
+
### TokenVerifier
|
| 74 |
|
| 75 |
+
`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.
|
| 76 |
|
| 77 |
+
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.
|
| 78 |
|
| 79 |
+
```python
|
| 80 |
from fastmcp import FastMCP
|
| 81 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 82 |
|
| 83 |
+
auth = JWTVerifier(
|
| 84 |
+
jwks_uri="https://your-auth-system.com/.well-known/jwks.json",
|
| 85 |
+
issuer="https://your-auth-system.com",
|
| 86 |
+
audience="your-mcp-server"
|
| 87 |
+
)
|
| 88 |
|
| 89 |
+
mcp = FastMCP(name="Protected Server", auth=auth)
|
| 90 |
```
|
| 91 |
|
| 92 |
+
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.
|
| 93 |
+
|
| 94 |
+
`TokenVerifier` works well when you control both the token issuer and your MCP server, or when integrating with existing JWT-based infrastructure.
|
| 95 |
+
|
| 96 |
+
→ **Complete guide**: [Token Verification](/servers/auth/token-verification)
|
| 97 |
+
|
| 98 |
+
### RemoteAuthProvider
|
| 99 |
+
|
| 100 |
+
`RemoteAuthProvider` combines token validation with OAuth discovery metadata, enabling MCP clients to automatically discover and authenticate with external identity providers.
|
| 101 |
+
|
| 102 |
+
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.
|
| 103 |
+
|
| 104 |
+
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.
|
| 105 |
+
|
| 106 |
+
```python
|
| 107 |
from fastmcp import FastMCP
|
| 108 |
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
| 109 |
|
| 110 |
+
auth = AuthKitProvider(
|
| 111 |
+
authkit_domain="https://your-project.authkit.app",
|
| 112 |
+
base_url="https://your-fastmcp-server.com"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
mcp = FastMCP(name="Enterprise Server", auth=auth)
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
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.
|
| 119 |
+
|
| 120 |
+
`RemoteAuthProvider` excels for production applications that need professional identity management without implementation complexity.
|
| 121 |
+
|
| 122 |
+
→ **Complete guide**: [Remote OAuth](/servers/auth/remote-oauth)
|
| 123 |
|
| 124 |
+
### OAuthProvider
|
| 125 |
+
|
| 126 |
+
`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.
|
| 127 |
+
|
| 128 |
+
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.
|
| 129 |
+
|
| 130 |
+
```python
|
| 131 |
+
from fastmcp import FastMCP
|
| 132 |
+
from fastmcp.server.auth.providers.oauth import MyOAuthProvider
|
| 133 |
+
|
| 134 |
+
auth = MyOAuthProvider(
|
| 135 |
+
user_store=your_user_database,
|
| 136 |
+
client_store=your_client_registry,
|
| 137 |
+
# Additional configuration...
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
mcp = FastMCP(name="Auth Server", auth=auth)
|
| 141 |
```
|
|
|
|
| 142 |
|
| 143 |
+
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.
|
| 144 |
+
|
| 145 |
+
`OAuthProvider` should be used only when you have specific requirements that external providers cannot meet and the expertise to implement OAuth securely.
|
| 146 |
+
|
| 147 |
+
→ **Complete guide**: [Full OAuth Server](/servers/auth/full-oauth-server)
|
| 148 |
+
|
| 149 |
+
## Configuration Approaches
|
| 150 |
|
| 151 |
+
FastMCP supports both programmatic configuration for maximum flexibility and environment-based configuration for deployment simplicity.
|
| 152 |
|
| 153 |
+
### Programmatic Configuration
|
| 154 |
|
| 155 |
+
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.
|
|
|
|
| 156 |
|
| 157 |
+
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.
|
| 158 |
+
|
| 159 |
+
### Environment Configuration
|
| 160 |
+
|
| 161 |
+
Environment-based configuration separates authentication settings from application code, enabling the same codebase to work across different deployment environments without modification.
|
| 162 |
+
|
| 163 |
+
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.
|
| 164 |
|
| 165 |
```bash
|
| 166 |
export FASTMCP_SERVER_AUTH=JWT
|
| 167 |
+
export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.example.com/jwks"
|
| 168 |
+
export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.example.com"
|
| 169 |
+
export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-server"
|
| 170 |
```
|
| 171 |
|
| 172 |
+
With these environment variables set, creating an authenticated FastMCP server requires no additional configuration:
|
| 173 |
|
| 174 |
```python
|
| 175 |
from fastmcp import FastMCP
|
| 176 |
|
| 177 |
+
# Authentication automatically configured from environment
|
| 178 |
+
mcp = FastMCP(name="My Server")
|
|
|
|
|
|
|
|
|
|
| 179 |
```
|
| 180 |
|
| 181 |
+
This approach simplifies deployment pipelines and follows twelve-factor app principles for configuration management.
|
| 182 |
+
|
| 183 |
+
## Choosing Your Implementation
|
| 184 |
+
|
| 185 |
+
The authentication approach you choose depends on your existing infrastructure, security requirements, and operational constraints.
|
| 186 |
+
|
| 187 |
+
**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.
|
| 188 |
+
|
| 189 |
+
**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.
|
| 190 |
+
|
| 191 |
+
**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.
|
| 192 |
+
|
| 193 |
+
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.
|
docs/servers/auth/full-oauth-server.mdx
CHANGED
|
@@ -11,119 +11,219 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
<Warning>
|
| 14 |
-
**This is an extremely advanced pattern.** Building a secure
|
| 15 |
|
| 16 |
-
|
| 17 |
</Warning>
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
"""
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
<Warning>
|
| 14 |
+
**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.
|
| 15 |
|
| 16 |
+
**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.
|
| 17 |
</Warning>
|
| 18 |
|
| 19 |
+
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.
|
| 20 |
+
|
| 21 |
+
This documentation exists for completeness - the vast majority of applications should use external identity providers instead.
|
| 22 |
+
|
| 23 |
+
## OAuthProvider
|
| 24 |
+
|
| 25 |
+
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.
|
| 26 |
+
|
| 27 |
+
<Note>
|
| 28 |
+
`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.
|
| 29 |
+
</Note>
|
| 30 |
+
|
| 31 |
+
## Required Implementation
|
| 32 |
+
|
| 33 |
+
You must implement these abstract methods to create a functioning OAuth server:
|
| 34 |
+
|
| 35 |
+
### Client Management
|
| 36 |
+
|
| 37 |
+
<Card icon="code" title="Client Management Methods">
|
| 38 |
+
<ParamField body="get_client" type="async method">
|
| 39 |
+
Retrieve client information by ID from your database.
|
| 40 |
+
|
| 41 |
+
<Expandable title="Parameters">
|
| 42 |
+
<ParamField body="client_id" type="str">
|
| 43 |
+
Client identifier to look up
|
| 44 |
+
</ParamField>
|
| 45 |
+
</Expandable>
|
| 46 |
+
|
| 47 |
+
<Expandable title="Returns">
|
| 48 |
+
<ParamField body="OAuthClientInformationFull | None" type="return type">
|
| 49 |
+
Client information object or `None` if client not found
|
| 50 |
+
</ParamField>
|
| 51 |
+
</Expandable>
|
| 52 |
+
</ParamField>
|
| 53 |
+
|
| 54 |
+
<ParamField body="register_client" type="async method">
|
| 55 |
+
Store new client registration information in your database.
|
| 56 |
+
|
| 57 |
+
<Expandable title="Parameters">
|
| 58 |
+
<ParamField body="client_info" type="OAuthClientInformationFull">
|
| 59 |
+
Complete client registration information to store
|
| 60 |
+
</ParamField>
|
| 61 |
+
</Expandable>
|
| 62 |
+
|
| 63 |
+
<Expandable title="Returns">
|
| 64 |
+
<ParamField body="None" type="return type">
|
| 65 |
+
No return value
|
| 66 |
+
</ParamField>
|
| 67 |
+
</Expandable>
|
| 68 |
+
</ParamField>
|
| 69 |
+
</Card>
|
| 70 |
+
|
| 71 |
+
### Authorization Flow
|
| 72 |
+
|
| 73 |
+
<Card icon="code" title="Authorization Flow Methods">
|
| 74 |
+
<ParamField body="authorize" type="async method">
|
| 75 |
+
Handle authorization request and return redirect URL. Must implement user authentication and consent collection.
|
| 76 |
+
|
| 77 |
+
<Expandable title="Parameters">
|
| 78 |
+
<ParamField body="client" type="OAuthClientInformationFull">
|
| 79 |
+
OAuth client making the authorization request
|
| 80 |
+
</ParamField>
|
| 81 |
+
<ParamField body="params" type="AuthorizationParams">
|
| 82 |
+
Authorization request parameters from the client
|
| 83 |
+
</ParamField>
|
| 84 |
+
</Expandable>
|
| 85 |
+
|
| 86 |
+
<Expandable title="Returns">
|
| 87 |
+
<ParamField body="str" type="return type">
|
| 88 |
+
Redirect URL to send the client to
|
| 89 |
+
</ParamField>
|
| 90 |
+
</Expandable>
|
| 91 |
+
</ParamField>
|
| 92 |
+
|
| 93 |
+
<ParamField body="load_authorization_code" type="async method">
|
| 94 |
+
Load authorization code from storage by code string. Return `None` if code is invalid or expired.
|
| 95 |
+
|
| 96 |
+
<Expandable title="Parameters">
|
| 97 |
+
<ParamField body="client" type="OAuthClientInformationFull">
|
| 98 |
+
OAuth client attempting to use the authorization code
|
| 99 |
+
</ParamField>
|
| 100 |
+
<ParamField body="authorization_code" type="str">
|
| 101 |
+
Authorization code string to look up
|
| 102 |
+
</ParamField>
|
| 103 |
+
</Expandable>
|
| 104 |
+
|
| 105 |
+
<Expandable title="Returns">
|
| 106 |
+
<ParamField body="AuthorizationCode | None" type="return type">
|
| 107 |
+
Authorization code object or `None` if not found
|
| 108 |
+
</ParamField>
|
| 109 |
+
</Expandable>
|
| 110 |
+
</ParamField>
|
| 111 |
+
</Card>
|
| 112 |
+
|
| 113 |
+
### Token Management
|
| 114 |
+
|
| 115 |
+
<Card icon="code" title="Token Management Methods">
|
| 116 |
+
<ParamField body="exchange_authorization_code" type="async method">
|
| 117 |
+
Exchange authorization code for access and refresh tokens. Must validate code and create new tokens.
|
| 118 |
+
|
| 119 |
+
<Expandable title="Parameters">
|
| 120 |
+
<ParamField body="client" type="OAuthClientInformationFull">
|
| 121 |
+
OAuth client exchanging the authorization code
|
| 122 |
+
</ParamField>
|
| 123 |
+
<ParamField body="authorization_code" type="AuthorizationCode">
|
| 124 |
+
Valid authorization code object to exchange
|
| 125 |
+
</ParamField>
|
| 126 |
+
</Expandable>
|
| 127 |
+
|
| 128 |
+
<Expandable title="Returns">
|
| 129 |
+
<ParamField body="OAuthToken" type="return type">
|
| 130 |
+
New OAuth token containing access and refresh tokens
|
| 131 |
+
</ParamField>
|
| 132 |
+
</Expandable>
|
| 133 |
+
</ParamField>
|
| 134 |
+
|
| 135 |
+
<ParamField body="load_refresh_token" type="async method">
|
| 136 |
+
Load refresh token from storage by token string. Return `None` if token is invalid or expired.
|
| 137 |
+
|
| 138 |
+
<Expandable title="Parameters">
|
| 139 |
+
<ParamField body="client" type="OAuthClientInformationFull">
|
| 140 |
+
OAuth client attempting to use the refresh token
|
| 141 |
+
</ParamField>
|
| 142 |
+
<ParamField body="refresh_token" type="str">
|
| 143 |
+
Refresh token string to look up
|
| 144 |
+
</ParamField>
|
| 145 |
+
</Expandable>
|
| 146 |
+
|
| 147 |
+
<Expandable title="Returns">
|
| 148 |
+
<ParamField body="RefreshToken | None" type="return type">
|
| 149 |
+
Refresh token object or `None` if not found
|
| 150 |
+
</ParamField>
|
| 151 |
+
</Expandable>
|
| 152 |
+
</ParamField>
|
| 153 |
+
|
| 154 |
+
<ParamField body="exchange_refresh_token" type="async method">
|
| 155 |
+
Exchange refresh token for new access/refresh token pair. Must validate scopes and token.
|
| 156 |
+
|
| 157 |
+
<Expandable title="Parameters">
|
| 158 |
+
<ParamField body="client" type="OAuthClientInformationFull">
|
| 159 |
+
OAuth client using the refresh token
|
| 160 |
+
</ParamField>
|
| 161 |
+
<ParamField body="refresh_token" type="RefreshToken">
|
| 162 |
+
Valid refresh token object to exchange
|
| 163 |
+
</ParamField>
|
| 164 |
+
<ParamField body="scopes" type="list[str]">
|
| 165 |
+
Requested scopes for the new access token
|
| 166 |
+
</ParamField>
|
| 167 |
+
</Expandable>
|
| 168 |
+
|
| 169 |
+
<Expandable title="Returns">
|
| 170 |
+
<ParamField body="OAuthToken" type="return type">
|
| 171 |
+
New OAuth token with updated access and refresh tokens
|
| 172 |
+
</ParamField>
|
| 173 |
+
</Expandable>
|
| 174 |
+
</ParamField>
|
| 175 |
+
|
| 176 |
+
<ParamField body="load_access_token" type="async method">
|
| 177 |
+
Load an access token by its token string.
|
| 178 |
+
|
| 179 |
+
<Expandable title="Parameters">
|
| 180 |
+
<ParamField body="token" type="str">
|
| 181 |
+
The access token to verify
|
| 182 |
+
</ParamField>
|
| 183 |
+
</Expandable>
|
| 184 |
+
|
| 185 |
+
<Expandable title="Returns">
|
| 186 |
+
<ParamField body="AccessToken | None" type="return type">
|
| 187 |
+
The access token object, or `None` if the token is invalid
|
| 188 |
+
</ParamField>
|
| 189 |
+
</Expandable>
|
| 190 |
+
</ParamField>
|
| 191 |
+
|
| 192 |
+
<ParamField body="revoke_token" type="async method">
|
| 193 |
+
Revoke access or refresh token, marking it as invalid in storage.
|
| 194 |
+
|
| 195 |
+
<Expandable title="Parameters">
|
| 196 |
+
<ParamField body="token" type="AccessToken | RefreshToken">
|
| 197 |
+
Token object to revoke and mark invalid
|
| 198 |
+
</ParamField>
|
| 199 |
+
</Expandable>
|
| 200 |
+
|
| 201 |
+
<Expandable title="Returns">
|
| 202 |
+
<ParamField body="None" type="return type">
|
| 203 |
+
No return value
|
| 204 |
+
</ParamField>
|
| 205 |
+
</Expandable>
|
| 206 |
+
</ParamField>
|
| 207 |
+
|
| 208 |
+
<ParamField body="verify_token" type="async method">
|
| 209 |
+
Verify bearer token for incoming requests. Return `AccessToken` if valid, `None` if invalid.
|
| 210 |
+
|
| 211 |
+
<Expandable title="Parameters">
|
| 212 |
+
<ParamField body="token" type="str">
|
| 213 |
+
Bearer token string from incoming request
|
| 214 |
+
</ParamField>
|
| 215 |
+
</Expandable>
|
| 216 |
+
|
| 217 |
+
<Expandable title="Returns">
|
| 218 |
+
<ParamField body="AccessToken | None" type="return type">
|
| 219 |
+
Access token object if valid, `None` if invalid or expired
|
| 220 |
+
</ParamField>
|
| 221 |
+
</Expandable>
|
| 222 |
+
</ParamField>
|
| 223 |
+
</Card>
|
| 224 |
+
|
| 225 |
+
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.
|
| 226 |
+
|
| 227 |
+
<Warning>
|
| 228 |
+
**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.
|
| 229 |
+
</Warning>
|
docs/servers/auth/remote-oauth.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
---
|
| 2 |
title: Remote OAuth
|
| 3 |
sidebarTitle: Remote OAuth
|
| 4 |
-
description: Integrate with external identity providers like WorkOS, Auth0,
|
| 5 |
icon: camera-cctv
|
| 6 |
tag: NEW
|
| 7 |
---
|
|
@@ -10,138 +10,180 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
```mermaid
|
| 24 |
sequenceDiagram
|
| 25 |
participant Client
|
| 26 |
-
participant FastMCPServer as FastMCP
|
| 27 |
-
participant ExternalIdP as
|
| 28 |
|
| 29 |
Client->>FastMCPServer: 1. GET /.well-known/oauth-protected-resource
|
| 30 |
FastMCPServer-->>Client: 2. "Use https://my-idp.com for auth"
|
| 31 |
|
| 32 |
note over Client, ExternalIdP: Client goes directly to the IdP
|
| 33 |
-
Client->>ExternalIdP: 3.
|
| 34 |
-
ExternalIdP-->>Client: 4.
|
| 35 |
|
| 36 |
-
Client->>
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
Client->>FastMCPServer: 6. MCP request with Bearer token
|
| 40 |
-
note right of FastMCPServer: Server verifies the token
|
| 41 |
-
FastMCPServer->>FastMCPServer: 7. Verify token signature <br/> (using IdP's public keys)
|
| 42 |
-
FastMCPServer-->>Client: 8. MCP Response
|
| 43 |
```
|
| 44 |
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
-
|
| 48 |
|
| 49 |
-
|
| 50 |
-
2. **Forwarding Metadata:** Tell MCP clients where to find the external provider's login pages and token endpoints.
|
| 51 |
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
```python
|
| 57 |
-
from fastmcp
|
|
|
|
| 58 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 59 |
-
from
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
return await self.token_verifier.verify_token(token)
|
| 73 |
```
|
| 74 |
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
|
| 79 |
```python
|
| 80 |
import httpx
|
| 81 |
from starlette.responses import JSONResponse
|
| 82 |
from starlette.routing import Route
|
| 83 |
|
| 84 |
-
class
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
def
|
| 88 |
-
|
| 89 |
-
async def protected_resource_metadata(request):
|
| 90 |
-
return JSONResponse({
|
| 91 |
-
"resource": "https://my-fastmcp-server.com",
|
| 92 |
-
"authorization_servers": ["https://my-idp.com"],
|
| 93 |
-
"bearer_methods_supported": ["header"],
|
| 94 |
-
})
|
| 95 |
|
| 96 |
-
|
|
|
|
| 97 |
|
| 98 |
-
#
|
| 99 |
-
# (Clients can also fetch this directly from the IdP)
|
| 100 |
async def authorization_server_metadata(request):
|
| 101 |
async with httpx.AsyncClient() as client:
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
return routes
|
|
|
|
|
|
|
| 108 |
```
|
| 109 |
|
| 110 |
-
|
| 111 |
|
| 112 |
-
|
|
|
|
|
|
|
| 113 |
|
| 114 |
```python
|
| 115 |
from fastmcp import FastMCP
|
|
|
|
| 116 |
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
```
|
| 119 |
|
|
|
|
| 120 |
|
| 121 |
-
|
| 122 |
|
| 123 |
-
|
| 124 |
|
| 125 |
-
|
| 126 |
-
1. A WorkOS account with an AuthKit project.
|
| 127 |
-
2. **Dynamic Client Registration (DCR)** must be enabled in your WorkOS application settings.
|
| 128 |
-
3. Your FastMCP server's URL must be added as a **Redirect URI** in your WorkOS project (can be localhost for development).
|
| 129 |
|
| 130 |
-
|
| 131 |
-
from fastmcp import FastMCP
|
| 132 |
-
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
| 133 |
|
| 134 |
-
|
| 135 |
-
auth_provider = AuthKitProvider(
|
| 136 |
-
# Your unique AuthKit domain from the WorkOS dashboard
|
| 137 |
-
authkit_domain="https://your-project.authkit.app",
|
| 138 |
-
# The URL of THIS FastMCP server (can be localhost for development)
|
| 139 |
-
base_url="https://your-fastmcp-server.com"
|
| 140 |
-
)
|
| 141 |
|
| 142 |
-
|
| 143 |
-
```
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Remote OAuth
|
| 3 |
sidebarTitle: Remote OAuth
|
| 4 |
+
description: Integrate your FastMCP server with external identity providers like WorkOS, Auth0, and corporate SSO systems.
|
| 5 |
icon: camera-cctv
|
| 6 |
tag: NEW
|
| 7 |
---
|
|
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
+
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.
|
| 14 |
|
| 15 |
+
<Tip>
|
| 16 |
+
Remote OAuth requires identity providers that support **Dynamic Client Registration (DCR)**. This enables MCP clients to automatically register and authenticate without manual configuration steps.
|
| 17 |
+
</Tip>
|
| 18 |
+
|
| 19 |
+
## The Remote OAuth Challenge
|
| 20 |
+
|
| 21 |
+
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.
|
| 22 |
+
|
| 23 |
+
This creates several unique requirements that standard OAuth implementations don't address well:
|
| 24 |
+
|
| 25 |
+
**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.
|
| 26 |
+
|
| 27 |
+
**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.
|
| 28 |
|
| 29 |
+
**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.
|
| 30 |
|
| 31 |
+
**Protocol Integration**: The authentication process must integrate cleanly with MCP's JSON-RPC transport layer and error handling mechanisms.
|
| 32 |
|
| 33 |
+
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.
|
| 34 |
+
|
| 35 |
+
## MCP Authentication Discovery
|
| 36 |
+
|
| 37 |
+
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.
|
| 38 |
+
|
| 39 |
+
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.
|
| 40 |
|
| 41 |
```mermaid
|
| 42 |
sequenceDiagram
|
| 43 |
participant Client
|
| 44 |
+
participant FastMCPServer as FastMCP Server
|
| 45 |
+
participant ExternalIdP as Identity Provider
|
| 46 |
|
| 47 |
Client->>FastMCPServer: 1. GET /.well-known/oauth-protected-resource
|
| 48 |
FastMCPServer-->>Client: 2. "Use https://my-idp.com for auth"
|
| 49 |
|
| 50 |
note over Client, ExternalIdP: Client goes directly to the IdP
|
| 51 |
+
Client->>ExternalIdP: 3. Authenticate & get token via DCR
|
| 52 |
+
ExternalIdP-->>Client: 4. Access token
|
| 53 |
|
| 54 |
+
Client->>FastMCPServer: 5. MCP request with Bearer token
|
| 55 |
+
FastMCPServer->>FastMCPServer: 6. Verify token signature
|
| 56 |
+
FastMCPServer-->>Client: 7. MCP response
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
```
|
| 58 |
|
| 59 |
+
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.
|
| 60 |
+
|
| 61 |
+
## FastMCP Remote Authentication
|
| 62 |
+
|
| 63 |
+
<VersionBadge version="2.11.1" />
|
| 64 |
|
| 65 |
+
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.
|
| 66 |
|
| 67 |
+
### RemoteAuthProvider
|
|
|
|
| 68 |
|
| 69 |
+
`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.
|
| 70 |
|
| 71 |
+
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.
|
| 72 |
+
|
| 73 |
+
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.
|
| 74 |
+
|
| 75 |
+
### Basic Implementation
|
| 76 |
+
|
| 77 |
+
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.
|
| 78 |
|
| 79 |
```python
|
| 80 |
+
from fastmcp import FastMCP
|
| 81 |
+
from fastmcp.server.auth import RemoteAuthProvider
|
| 82 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 83 |
+
from pydantic import AnyHttpUrl
|
| 84 |
|
| 85 |
+
# Configure token validation for your identity provider
|
| 86 |
+
token_verifier = JWTVerifier(
|
| 87 |
+
jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json",
|
| 88 |
+
issuer="https://auth.yourcompany.com",
|
| 89 |
+
audience="mcp-production-api"
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
# Create the remote auth provider
|
| 93 |
+
auth = RemoteAuthProvider(
|
| 94 |
+
token_verifier=token_verifier,
|
| 95 |
+
authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")],
|
| 96 |
+
resource_server_url="https://api.yourcompany.com"
|
| 97 |
+
)
|
| 98 |
|
| 99 |
+
mcp = FastMCP(name="Company API", auth=auth)
|
|
|
|
| 100 |
```
|
| 101 |
|
| 102 |
+
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.
|
| 103 |
+
|
| 104 |
+
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.
|
| 105 |
+
|
| 106 |
+
### Custom Endpoints
|
| 107 |
|
| 108 |
+
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.
|
| 109 |
|
| 110 |
```python
|
| 111 |
import httpx
|
| 112 |
from starlette.responses import JSONResponse
|
| 113 |
from starlette.routing import Route
|
| 114 |
|
| 115 |
+
class CompanyAuthProvider(RemoteAuthProvider):
|
| 116 |
+
def __init__(self):
|
| 117 |
+
token_verifier = JWTVerifier(
|
| 118 |
+
jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json",
|
| 119 |
+
issuer="https://auth.yourcompany.com",
|
| 120 |
+
audience="mcp-production-api"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
super().__init__(
|
| 124 |
+
token_verifier=token_verifier,
|
| 125 |
+
authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")],
|
| 126 |
+
resource_server_url="https://api.yourcompany.com"
|
| 127 |
+
)
|
| 128 |
|
| 129 |
+
def get_routes(self) -> list[Route]:
|
| 130 |
+
"""Add custom endpoints to the standard protected resource routes."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
+
# Get the standard OAuth protected resource routes
|
| 133 |
+
routes = super().get_routes()
|
| 134 |
|
| 135 |
+
# Add authorization server metadata forwarding for client convenience
|
|
|
|
| 136 |
async def authorization_server_metadata(request):
|
| 137 |
async with httpx.AsyncClient() as client:
|
| 138 |
+
response = await client.get(
|
| 139 |
+
"https://auth.yourcompany.com/.well-known/oauth-authorization-server"
|
| 140 |
+
)
|
| 141 |
+
response.raise_for_status()
|
| 142 |
+
return JSONResponse(response.json())
|
| 143 |
+
|
| 144 |
+
routes.append(
|
| 145 |
+
Route("/.well-known/oauth-authorization-server", authorization_server_metadata)
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
return routes
|
| 149 |
+
|
| 150 |
+
mcp = FastMCP(name="Company API", auth=CompanyAuthProvider())
|
| 151 |
```
|
| 152 |
|
| 153 |
+
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.
|
| 154 |
|
| 155 |
+
## WorkOS AuthKit Integration
|
| 156 |
+
|
| 157 |
+
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.
|
| 158 |
|
| 159 |
```python
|
| 160 |
from fastmcp import FastMCP
|
| 161 |
+
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
| 162 |
|
| 163 |
+
auth = AuthKitProvider(
|
| 164 |
+
authkit_domain="https://your-project.authkit.app",
|
| 165 |
+
base_url="https://your-mcp-server.com"
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
mcp = FastMCP(name="Protected Application", auth=auth)
|
| 169 |
```
|
| 170 |
|
| 171 |
+
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.
|
| 172 |
|
| 173 |
+
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.
|
| 174 |
|
| 175 |
+
→ **Complete WorkOS tutorial**: [AuthKit Integration Guide](/integrations/authkit)
|
| 176 |
|
| 177 |
+
## Implementation Considerations
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
Remote OAuth integration requires careful attention to several technical details that affect reliability and security.
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
**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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
+
**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.
|
|
|
|
| 184 |
|
| 185 |
+
**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.
|
| 186 |
+
|
| 187 |
+
**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.
|
| 188 |
+
|
| 189 |
+
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.
|
docs/servers/auth/token-verification.mdx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
---
|
| 2 |
title: Token Verification
|
| 3 |
sidebarTitle: Token Verification
|
| 4 |
-
description: Protect your server by validating bearer tokens.
|
| 5 |
icon: key
|
| 6 |
tag: NEW
|
| 7 |
---
|
|
@@ -10,202 +10,191 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
##
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
-
The
|
| 24 |
|
| 25 |
-
|
| 26 |
-
```python Using a JWKS Endpoint (Recommended)
|
| 27 |
-
from fastmcp import FastMCP
|
| 28 |
-
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 29 |
|
| 30 |
-
|
| 31 |
-
# to validate incoming tokens.
|
| 32 |
-
verifier = JWTVerifier(
|
| 33 |
-
jwks_uri="https://my-identity-provider.com/.well-known/jwks.json",
|
| 34 |
-
issuer="https://my-identity-provider.com/",
|
| 35 |
-
audience="my-mcp-server-identifier"
|
| 36 |
-
)
|
| 37 |
|
| 38 |
-
|
| 39 |
-
```
|
| 40 |
|
| 41 |
-
|
| 42 |
-
from fastmcp import FastMCP
|
| 43 |
-
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 44 |
|
| 45 |
-
|
| 46 |
-
# Use this only if a JWKS endpoint is not available.
|
| 47 |
-
public_key_pem = """-----BEGIN PUBLIC KEY-----
|
| 48 |
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy...
|
| 49 |
-
-----END PUBLIC KEY-----"""
|
| 50 |
|
| 51 |
-
|
| 52 |
-
public_key=public_key_pem,
|
| 53 |
-
issuer="https://my-identity-provider.com/",
|
| 54 |
-
audience="my-mcp-server-identifier"
|
| 55 |
-
)
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
</ResponseField>
|
| 83 |
-
|
| 84 |
-
<ResponseField name="required_scopes" type="list[str] | None">
|
| 85 |
-
List of scopes that all tokens must have. Tokens missing any required scope will be rejected.
|
| 86 |
-
</ResponseField>
|
| 87 |
-
|
| 88 |
-
<ResponseField name="resource_server_url" type="str | None">
|
| 89 |
-
Resource server URL for OAuth protocol compliance
|
| 90 |
-
</ResponseField>
|
| 91 |
-
</Expandable>
|
| 92 |
-
</ResponseField>
|
| 93 |
-
</Card>
|
| 94 |
-
|
| 95 |
-
### Environment Variable Configuration
|
| 96 |
-
|
| 97 |
-
You can configure the `JWTVerifier` entirely through environment variables. Set `FASTMCP_SERVER_AUTH=JWT` to enable automatic configuration, then provide the verifier's settings.
|
| 98 |
-
|
| 99 |
-
Example configuration:
|
| 100 |
-
```bash
|
| 101 |
-
export FASTMCP_SERVER_AUTH=JWT
|
| 102 |
-
export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://your-idp.com/.well-known/jwks.json"
|
| 103 |
-
export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://your-idp.com"
|
| 104 |
-
export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="your-server-id"
|
| 105 |
-
```
|
| 106 |
|
| 107 |
-
Your FastMCP server will now be automatically configured with JWT verification:
|
| 108 |
```python
|
| 109 |
from fastmcp import FastMCP
|
|
|
|
| 110 |
|
| 111 |
-
#
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
PEM-encoded public key for JWT verification. Use this for static public key configuration.
|
| 119 |
-
</ParamField>
|
| 120 |
|
| 121 |
-
|
| 122 |
-
URI to fetch JSON Web Key Set (JWKS). Use this for automatic key rotation support.
|
| 123 |
-
</ParamField>
|
| 124 |
|
| 125 |
-
|
| 126 |
-
Expected JWT issuer claim for validation
|
| 127 |
-
</ParamField>
|
| 128 |
|
| 129 |
-
|
| 130 |
-
Expected JWT audience claim for validation
|
| 131 |
-
</ParamField>
|
| 132 |
|
| 133 |
-
|
| 134 |
-
JWT signing algorithm (e.g., RS256, HS256, ES256)
|
| 135 |
-
</ParamField>
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
-
|
|
|
|
| 149 |
|
| 150 |
-
|
| 151 |
|
| 152 |
-
|
| 153 |
|
|
|
|
| 154 |
|
| 155 |
-
|
| 156 |
-
**Never use static token verification in production.** Tokens are stored as plain text strings, which is highly insecure.
|
| 157 |
-
</Warning>
|
| 158 |
|
| 159 |
-
|
| 160 |
|
| 161 |
```python
|
| 162 |
from fastmcp import FastMCP
|
| 163 |
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
|
| 164 |
|
|
|
|
| 165 |
verifier = StaticTokenVerifier(
|
| 166 |
tokens={
|
| 167 |
-
"dev-
|
| 168 |
-
"client_id": "alice@
|
| 169 |
-
"scopes": ["read:data", "write:data"]
|
| 170 |
},
|
| 171 |
-
"
|
| 172 |
"client_id": "guest-user",
|
| 173 |
"scopes": ["read:data"]
|
| 174 |
}
|
| 175 |
},
|
| 176 |
-
required_scopes=["read:data"]
|
| 177 |
)
|
| 178 |
|
| 179 |
mcp = FastMCP(name="Development Server", auth=verifier)
|
| 180 |
```
|
| 181 |
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
|
| 185 |
|
| 186 |
```python
|
| 187 |
-
from fastmcp import FastMCP
|
| 188 |
from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
|
| 189 |
|
| 190 |
-
#
|
| 191 |
key_pair = RSAKeyPair.generate()
|
| 192 |
|
| 193 |
-
#
|
| 194 |
-
|
| 195 |
public_key=key_pair.public_key,
|
| 196 |
-
issuer="https://
|
| 197 |
-
audience="test-server"
|
| 198 |
)
|
| 199 |
-
mcp = FastMCP(name="Test Server", auth=auth_verifier)
|
| 200 |
|
| 201 |
-
#
|
| 202 |
test_token = key_pair.create_token(
|
| 203 |
subject="test-user-123",
|
| 204 |
-
issuer="https://
|
| 205 |
-
audience="test-server",
|
| 206 |
-
scopes=["read", "write"]
|
| 207 |
)
|
| 208 |
|
| 209 |
-
print(f"
|
| 210 |
```
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Token Verification
|
| 3 |
sidebarTitle: Token Verification
|
| 4 |
+
description: Protect your server by validating bearer tokens issued by external systems.
|
| 5 |
icon: key
|
| 6 |
tag: NEW
|
| 7 |
---
|
|
|
|
| 10 |
|
| 11 |
<VersionBadge version="2.11.0" />
|
| 12 |
|
| 13 |
+
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.
|
| 14 |
|
| 15 |
+
<Note>
|
| 16 |
+
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.
|
| 17 |
+
</Note>
|
| 18 |
|
| 19 |
+
## Understanding Token Verification
|
| 20 |
|
| 21 |
+
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.
|
| 22 |
|
| 23 |
+
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.
|
| 24 |
|
| 25 |
+
### The Token Verification Model
|
| 26 |
|
| 27 |
+
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.
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
**Token Issuance**: Another system (API gateway, authentication service, or identity provider) handles user authentication and creates signed tokens containing identity and permission information.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
**Token Validation**: Your MCP server receives these tokens, verifies their authenticity using cryptographic signatures, and extracts authorization information from their claims.
|
|
|
|
| 32 |
|
| 33 |
+
**Access Control**: Based on token contents, your server determines what resources, tools, and prompts the client can access.
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
### Token Security Considerations
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
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.
|
| 40 |
+
|
| 41 |
+
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.
|
| 42 |
+
|
| 43 |
+
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.
|
| 44 |
+
|
| 45 |
+
## FastMCP Token Verification
|
| 46 |
+
|
| 47 |
+
FastMCP provides the `TokenVerifier` class to handle token validation complexity while remaining flexible about token sources and validation strategies.
|
| 48 |
+
|
| 49 |
+
### TokenVerifier Design
|
| 50 |
+
|
| 51 |
+
`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.
|
| 52 |
+
|
| 53 |
+
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.
|
| 54 |
+
|
| 55 |
+
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.
|
| 56 |
+
|
| 57 |
+
### JWT Token Verification
|
| 58 |
+
|
| 59 |
+
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.
|
| 60 |
+
|
| 61 |
+
#### JWKS Endpoint Integration
|
| 62 |
+
|
| 63 |
+
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
|
|
|
| 65 |
```python
|
| 66 |
from fastmcp import FastMCP
|
| 67 |
+
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 68 |
|
| 69 |
+
# Configure JWT verification against your identity provider
|
| 70 |
+
verifier = JWTVerifier(
|
| 71 |
+
jwks_uri="https://auth.yourcompany.com/.well-known/jwks.json",
|
| 72 |
+
issuer="https://auth.yourcompany.com",
|
| 73 |
+
audience="mcp-production-api"
|
| 74 |
+
)
|
| 75 |
|
| 76 |
+
mcp = FastMCP(name="Protected API", auth=verifier)
|
| 77 |
+
```
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
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.
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
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.
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
#### Static Public Key Verification
|
|
|
|
|
|
|
| 84 |
|
| 85 |
+
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.
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
```python
|
| 88 |
+
from fastmcp import FastMCP
|
| 89 |
+
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 90 |
|
| 91 |
+
# Use a static public key for token verification
|
| 92 |
+
public_key_pem = """-----BEGIN PUBLIC KEY-----
|
| 93 |
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
| 94 |
+
-----END PUBLIC KEY-----"""
|
| 95 |
|
| 96 |
+
verifier = JWTVerifier(
|
| 97 |
+
public_key=public_key_pem,
|
| 98 |
+
issuer="https://auth.yourcompany.com",
|
| 99 |
+
audience="mcp-production-api"
|
| 100 |
+
)
|
| 101 |
|
| 102 |
+
mcp = FastMCP(name="Protected API", auth=verifier)
|
| 103 |
+
```
|
| 104 |
|
| 105 |
+
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.
|
| 106 |
|
| 107 |
+
### Development and Testing
|
| 108 |
|
| 109 |
+
Development environments often need simpler token management without the complexity of full JWT infrastructure. FastMCP provides tools specifically designed for these scenarios.
|
| 110 |
|
| 111 |
+
#### Static Token Verification
|
|
|
|
|
|
|
| 112 |
|
| 113 |
+
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.
|
| 114 |
|
| 115 |
```python
|
| 116 |
from fastmcp import FastMCP
|
| 117 |
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
|
| 118 |
|
| 119 |
+
# Define development tokens and their associated claims
|
| 120 |
verifier = StaticTokenVerifier(
|
| 121 |
tokens={
|
| 122 |
+
"dev-alice-token": {
|
| 123 |
+
"client_id": "alice@company.com",
|
| 124 |
+
"scopes": ["read:data", "write:data", "admin:users"]
|
| 125 |
},
|
| 126 |
+
"dev-guest-token": {
|
| 127 |
"client_id": "guest-user",
|
| 128 |
"scopes": ["read:data"]
|
| 129 |
}
|
| 130 |
},
|
| 131 |
+
required_scopes=["read:data"]
|
| 132 |
)
|
| 133 |
|
| 134 |
mcp = FastMCP(name="Development Server", auth=verifier)
|
| 135 |
```
|
| 136 |
|
| 137 |
+
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.
|
| 138 |
+
|
| 139 |
+
<Warning>
|
| 140 |
+
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.
|
| 141 |
+
</Warning>
|
| 142 |
+
|
| 143 |
+
#### Test Token Generation
|
| 144 |
|
| 145 |
+
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.
|
| 146 |
|
| 147 |
```python
|
|
|
|
| 148 |
from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
|
| 149 |
|
| 150 |
+
# Generate a key pair for testing
|
| 151 |
key_pair = RSAKeyPair.generate()
|
| 152 |
|
| 153 |
+
# Configure your server with the public key
|
| 154 |
+
verifier = JWTVerifier(
|
| 155 |
public_key=key_pair.public_key,
|
| 156 |
+
issuer="https://test.yourcompany.com",
|
| 157 |
+
audience="test-mcp-server"
|
| 158 |
)
|
|
|
|
| 159 |
|
| 160 |
+
# Generate a test token using the private key
|
| 161 |
test_token = key_pair.create_token(
|
| 162 |
subject="test-user-123",
|
| 163 |
+
issuer="https://test.yourcompany.com",
|
| 164 |
+
audience="test-mcp-server",
|
| 165 |
+
scopes=["read", "write", "admin"]
|
| 166 |
)
|
| 167 |
|
| 168 |
+
print(f"Test token: {test_token}")
|
| 169 |
```
|
| 170 |
|
| 171 |
+
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.
|
| 172 |
+
|
| 173 |
+
## Environment Configuration
|
| 174 |
+
|
| 175 |
+
FastMCP supports both programmatic and environment-based configuration for token verification, enabling flexible deployment across different environments.
|
| 176 |
+
|
| 177 |
+
Environment-based configuration separates authentication settings from application code, following twelve-factor app principles and simplifying deployment pipelines.
|
| 178 |
+
|
| 179 |
+
```bash
|
| 180 |
+
# Enable JWT verification
|
| 181 |
+
export FASTMCP_SERVER_AUTH=JWT
|
| 182 |
+
|
| 183 |
+
# Configure JWT verification parameters
|
| 184 |
+
export FASTMCP_SERVER_AUTH_JWT_JWKS_URI="https://auth.company.com/.well-known/jwks.json"
|
| 185 |
+
export FASTMCP_SERVER_AUTH_JWT_ISSUER="https://auth.company.com"
|
| 186 |
+
export FASTMCP_SERVER_AUTH_JWT_AUDIENCE="mcp-production-api"
|
| 187 |
+
export FASTMCP_SERVER_AUTH_JWT_REQUIRED_SCOPES="read:data,write:data"
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
With these environment variables configured, your FastMCP server automatically enables JWT verification:
|
| 191 |
+
|
| 192 |
+
```python
|
| 193 |
+
from fastmcp import FastMCP
|
| 194 |
+
|
| 195 |
+
# Authentication automatically configured from environment
|
| 196 |
+
mcp = FastMCP(name="Production API")
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
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.
|
| 200 |
+
|
src/fastmcp/server/auth/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from .auth import OAuthProvider, TokenVerifier
|
| 2 |
from .providers.jwt import JWTVerifier, StaticTokenVerifier
|
| 3 |
|
| 4 |
|
|
@@ -7,6 +7,7 @@ __all__ = [
|
|
| 7 |
"TokenVerifier",
|
| 8 |
"JWTVerifier",
|
| 9 |
"StaticTokenVerifier",
|
|
|
|
| 10 |
]
|
| 11 |
|
| 12 |
|
|
|
|
| 1 |
+
from .auth import OAuthProvider, TokenVerifier, RemoteAuthProvider
|
| 2 |
from .providers.jwt import JWTVerifier, StaticTokenVerifier
|
| 3 |
|
| 4 |
|
|
|
|
| 7 |
"TokenVerifier",
|
| 8 |
"JWTVerifier",
|
| 9 |
"StaticTokenVerifier",
|
| 10 |
+
"RemoteAuthProvider",
|
| 11 |
]
|
| 12 |
|
| 13 |
|
src/fastmcp/server/auth/auth.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
from typing import TYPE_CHECKING
|
| 4 |
-
|
| 5 |
from mcp.server.auth.provider import (
|
| 6 |
AccessToken,
|
| 7 |
AuthorizationCode,
|
|
@@ -11,6 +9,10 @@ from mcp.server.auth.provider import (
|
|
| 11 |
from mcp.server.auth.provider import (
|
| 12 |
TokenVerifier as TokenVerifierProtocol,
|
| 13 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from mcp.server.auth.settings import (
|
| 15 |
ClientRegistrationOptions,
|
| 16 |
RevocationOptions,
|
|
@@ -18,11 +20,8 @@ from mcp.server.auth.settings import (
|
|
| 18 |
from pydantic import AnyHttpUrl
|
| 19 |
from starlette.routing import Route
|
| 20 |
|
| 21 |
-
if TYPE_CHECKING:
|
| 22 |
-
pass
|
| 23 |
-
|
| 24 |
|
| 25 |
-
class AuthProvider:
|
| 26 |
"""Base class for all FastMCP authentication providers.
|
| 27 |
|
| 28 |
This class provides a unified interface for all authentication providers,
|
|
@@ -31,9 +30,18 @@ class AuthProvider:
|
|
| 31 |
custom authentication routes.
|
| 32 |
"""
|
| 33 |
|
| 34 |
-
def __init__(self,
|
| 35 |
-
"""
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
async def verify_token(self, token: str) -> AccessToken | None:
|
| 39 |
"""Verify a bearer token and return access info if valid.
|
|
@@ -48,22 +56,34 @@ class AuthProvider:
|
|
| 48 |
"""
|
| 49 |
raise NotImplementedError("Subclasses must implement verify_token")
|
| 50 |
|
| 51 |
-
def
|
| 52 |
-
"""
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
Returns:
|
| 61 |
-
List of routes
|
| 62 |
"""
|
| 63 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
|
|
|
| 67 |
"""Base class for token verifiers (Resource Servers).
|
| 68 |
|
| 69 |
This class provides token verification capability without OAuth server functionality.
|
|
@@ -79,26 +99,71 @@ class TokenVerifier(AuthProvider, TokenVerifierProtocol):
|
|
| 79 |
Initialize the token verifier.
|
| 80 |
|
| 81 |
Args:
|
| 82 |
-
resource_server_url: The URL of this resource server
|
|
|
|
|
|
|
| 83 |
required_scopes: Scopes that are required for all requests
|
| 84 |
"""
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
# Handle our own resource_server_url and required_scopes
|
| 89 |
-
self.resource_server_url: AnyHttpUrl | None
|
| 90 |
-
if resource_server_url is None:
|
| 91 |
-
self.resource_server_url = None
|
| 92 |
-
elif isinstance(resource_server_url, str):
|
| 93 |
-
self.resource_server_url = AnyHttpUrl(resource_server_url)
|
| 94 |
-
else:
|
| 95 |
-
self.resource_server_url = resource_server_url
|
| 96 |
|
| 97 |
async def verify_token(self, token: str) -> AccessToken | None:
|
| 98 |
"""Verify a bearer token and return access info if valid."""
|
| 99 |
raise NotImplementedError("Subclasses must implement verify_token")
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
class OAuthProvider(
|
| 103 |
AuthProvider,
|
| 104 |
OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken],
|
|
@@ -181,17 +246,33 @@ class OAuthProvider(
|
|
| 181 |
"""
|
| 182 |
return await self.load_access_token(token)
|
| 183 |
|
| 184 |
-
def
|
| 185 |
-
"""
|
| 186 |
|
| 187 |
-
This method
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
Args:
|
| 192 |
-
routes: List of standard OAuth routes from create_auth_routes
|
| 193 |
|
| 194 |
Returns:
|
| 195 |
-
List of
|
| 196 |
"""
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
|
|
|
| 3 |
from mcp.server.auth.provider import (
|
| 4 |
AccessToken,
|
| 5 |
AuthorizationCode,
|
|
|
|
| 9 |
from mcp.server.auth.provider import (
|
| 10 |
TokenVerifier as TokenVerifierProtocol,
|
| 11 |
)
|
| 12 |
+
from mcp.server.auth.routes import (
|
| 13 |
+
create_auth_routes,
|
| 14 |
+
create_protected_resource_routes,
|
| 15 |
+
)
|
| 16 |
from mcp.server.auth.settings import (
|
| 17 |
ClientRegistrationOptions,
|
| 18 |
RevocationOptions,
|
|
|
|
| 20 |
from pydantic import AnyHttpUrl
|
| 21 |
from starlette.routing import Route
|
| 22 |
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
class AuthProvider(TokenVerifierProtocol):
|
| 25 |
"""Base class for all FastMCP authentication providers.
|
| 26 |
|
| 27 |
This class provides a unified interface for all authentication providers,
|
|
|
|
| 30 |
custom authentication routes.
|
| 31 |
"""
|
| 32 |
|
| 33 |
+
def __init__(self, resource_server_url: AnyHttpUrl | str | None = None):
|
| 34 |
+
"""
|
| 35 |
+
Initialize the auth provider.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
resource_server_url: The URL of this resource server. This is used
|
| 39 |
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
| 40 |
+
header.
|
| 41 |
+
"""
|
| 42 |
+
if isinstance(resource_server_url, str):
|
| 43 |
+
resource_server_url = AnyHttpUrl(resource_server_url)
|
| 44 |
+
self.resource_server_url = resource_server_url
|
| 45 |
|
| 46 |
async def verify_token(self, token: str) -> AccessToken | None:
|
| 47 |
"""Verify a bearer token and return access info if valid.
|
|
|
|
| 56 |
"""
|
| 57 |
raise NotImplementedError("Subclasses must implement verify_token")
|
| 58 |
|
| 59 |
+
def get_routes(self) -> list[Route]:
|
| 60 |
+
"""Get the routes for this authentication provider.
|
| 61 |
|
| 62 |
+
Each provider is responsible for creating whatever routes it needs:
|
| 63 |
+
- TokenVerifier: typically no routes (default implementation)
|
| 64 |
+
- RemoteAuthProvider: protected resource metadata routes
|
| 65 |
+
- OAuthProvider: full OAuth authorization server routes
|
| 66 |
+
- Custom providers: whatever routes they need
|
| 67 |
|
| 68 |
Returns:
|
| 69 |
+
List of routes for this provider
|
| 70 |
"""
|
| 71 |
+
return []
|
| 72 |
+
|
| 73 |
+
def get_resource_metadata_url(self) -> AnyHttpUrl | None:
|
| 74 |
+
"""Get the resource metadata URL for RFC 9728 compliance."""
|
| 75 |
+
if self.resource_server_url is None:
|
| 76 |
+
return None
|
| 77 |
|
| 78 |
+
# Add .well-known path for RFC 9728 compliance
|
| 79 |
+
resource_metadata_url = AnyHttpUrl(
|
| 80 |
+
str(self.resource_server_url).rstrip("/")
|
| 81 |
+
+ "/.well-known/oauth-protected-resource"
|
| 82 |
+
)
|
| 83 |
+
return resource_metadata_url
|
| 84 |
|
| 85 |
+
|
| 86 |
+
class TokenVerifier(AuthProvider):
|
| 87 |
"""Base class for token verifiers (Resource Servers).
|
| 88 |
|
| 89 |
This class provides token verification capability without OAuth server functionality.
|
|
|
|
| 99 |
Initialize the token verifier.
|
| 100 |
|
| 101 |
Args:
|
| 102 |
+
resource_server_url: The URL of this resource server. This is used
|
| 103 |
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
| 104 |
+
header.
|
| 105 |
required_scopes: Scopes that are required for all requests
|
| 106 |
"""
|
| 107 |
+
super().__init__(resource_server_url=resource_server_url)
|
| 108 |
+
self.required_scopes = required_scopes or []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
async def verify_token(self, token: str) -> AccessToken | None:
|
| 111 |
"""Verify a bearer token and return access info if valid."""
|
| 112 |
raise NotImplementedError("Subclasses must implement verify_token")
|
| 113 |
|
| 114 |
|
| 115 |
+
class RemoteAuthProvider(AuthProvider):
|
| 116 |
+
"""Authentication provider for resource servers that verify tokens from known authorization servers.
|
| 117 |
+
|
| 118 |
+
This provider composes a TokenVerifier with authorization server metadata to create
|
| 119 |
+
standardized OAuth 2.0 Protected Resource endpoints (RFC 9728). Perfect for:
|
| 120 |
+
- JWT verification with known issuers
|
| 121 |
+
- Remote token introspection services
|
| 122 |
+
- Any resource server that knows where its tokens come from
|
| 123 |
+
|
| 124 |
+
Use this when you have token verification logic and want to advertise
|
| 125 |
+
the authorization servers that issue valid tokens.
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
def __init__(
|
| 129 |
+
self,
|
| 130 |
+
token_verifier: TokenVerifier,
|
| 131 |
+
authorization_servers: list[AnyHttpUrl],
|
| 132 |
+
resource_server_url: AnyHttpUrl | str,
|
| 133 |
+
):
|
| 134 |
+
"""Initialize the remote auth provider.
|
| 135 |
+
|
| 136 |
+
Args:
|
| 137 |
+
token_verifier: TokenVerifier instance for token validation
|
| 138 |
+
authorization_servers: List of authorization servers that issue valid tokens
|
| 139 |
+
resource_server_url: URL of this resource server. This is used
|
| 140 |
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
| 141 |
+
header.
|
| 142 |
+
"""
|
| 143 |
+
super().__init__(resource_server_url=resource_server_url)
|
| 144 |
+
self.token_verifier = token_verifier
|
| 145 |
+
self.authorization_servers = authorization_servers
|
| 146 |
+
|
| 147 |
+
async def verify_token(self, token: str) -> AccessToken | None:
|
| 148 |
+
"""Verify token using the configured token verifier."""
|
| 149 |
+
return await self.token_verifier.verify_token(token)
|
| 150 |
+
|
| 151 |
+
def get_routes(self) -> list[Route]:
|
| 152 |
+
"""Get OAuth routes for this provider.
|
| 153 |
+
|
| 154 |
+
By default, returns only the standardized OAuth 2.0 Protected Resource routes.
|
| 155 |
+
Subclasses can override this method to add additional routes by calling
|
| 156 |
+
super().get_routes() and extending the returned list.
|
| 157 |
+
"""
|
| 158 |
+
assert self.resource_server_url is not None
|
| 159 |
+
|
| 160 |
+
return create_protected_resource_routes(
|
| 161 |
+
resource_url=self.resource_server_url,
|
| 162 |
+
authorization_servers=self.authorization_servers,
|
| 163 |
+
scopes_supported=self.token_verifier.required_scopes,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
class OAuthProvider(
|
| 168 |
AuthProvider,
|
| 169 |
OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken],
|
|
|
|
| 246 |
"""
|
| 247 |
return await self.load_access_token(token)
|
| 248 |
|
| 249 |
+
def get_routes(self) -> list[Route]:
|
| 250 |
+
"""Get OAuth authorization server routes and optional protected resource routes.
|
| 251 |
|
| 252 |
+
This method creates the full set of OAuth routes including:
|
| 253 |
+
- Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.)
|
| 254 |
+
- Optional protected resource routes if resource_server_url is configured
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
Returns:
|
| 257 |
+
List of OAuth routes
|
| 258 |
"""
|
| 259 |
+
|
| 260 |
+
# Create standard OAuth authorization server routes
|
| 261 |
+
oauth_routes = create_auth_routes(
|
| 262 |
+
provider=self,
|
| 263 |
+
issuer_url=self.issuer_url,
|
| 264 |
+
service_documentation_url=self.service_documentation_url,
|
| 265 |
+
client_registration_options=self.client_registration_options,
|
| 266 |
+
revocation_options=self.revocation_options,
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
# Add protected resource routes if this server is also acting as a resource server
|
| 270 |
+
if self.resource_server_url:
|
| 271 |
+
protected_routes = create_protected_resource_routes(
|
| 272 |
+
resource_url=self.resource_server_url,
|
| 273 |
+
authorization_servers=[self.issuer_url],
|
| 274 |
+
scopes_supported=self.required_scopes,
|
| 275 |
+
)
|
| 276 |
+
oauth_routes.extend(protected_routes)
|
| 277 |
+
|
| 278 |
+
return oauth_routes
|
src/fastmcp/server/auth/providers/jwt.py
CHANGED
|
@@ -16,7 +16,7 @@ from pydantic import AnyHttpUrl, SecretStr
|
|
| 16 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 17 |
from typing_extensions import TypedDict
|
| 18 |
|
| 19 |
-
from fastmcp.server.auth
|
| 20 |
from fastmcp.server.auth.registry import register_provider
|
| 21 |
from fastmcp.utilities.logging import get_logger
|
| 22 |
from fastmcp.utilities.types import NotSet, NotSetT
|
|
|
|
| 16 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 17 |
from typing_extensions import TypedDict
|
| 18 |
|
| 19 |
+
from fastmcp.server.auth import TokenVerifier
|
| 20 |
from fastmcp.server.auth.registry import register_provider
|
| 21 |
from fastmcp.utilities.logging import get_logger
|
| 22 |
from fastmcp.utilities.types import NotSet, NotSetT
|
src/fastmcp/server/auth/providers/workos.py
CHANGED
|
@@ -1,15 +1,12 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import httpx
|
| 4 |
-
from mcp.server.auth.provider import (
|
| 5 |
-
AccessToken,
|
| 6 |
-
)
|
| 7 |
from pydantic import AnyHttpUrl
|
| 8 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 9 |
from starlette.responses import JSONResponse
|
| 10 |
-
from starlette.routing import
|
| 11 |
|
| 12 |
-
from fastmcp.server.auth
|
| 13 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 14 |
from fastmcp.server.auth.registry import register_provider
|
| 15 |
from fastmcp.utilities.logging import get_logger
|
|
@@ -31,7 +28,7 @@ class AuthKitProviderSettings(BaseSettings):
|
|
| 31 |
|
| 32 |
|
| 33 |
@register_provider("AUTHKIT")
|
| 34 |
-
class AuthKitProvider(
|
| 35 |
"""AuthKit metadata provider for DCR (Dynamic Client Registration).
|
| 36 |
|
| 37 |
This provider implements AuthKit integration using metadata forwarding
|
|
@@ -83,8 +80,6 @@ class AuthKitProvider(AuthProvider):
|
|
| 83 |
required_scopes: Optional list of scopes to require for all requests
|
| 84 |
token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit
|
| 85 |
"""
|
| 86 |
-
super().__init__()
|
| 87 |
-
|
| 88 |
settings = AuthKitProviderSettings.model_validate(
|
| 89 |
{
|
| 90 |
k: v
|
|
@@ -109,19 +104,21 @@ class AuthKitProvider(AuthProvider):
|
|
| 109 |
required_scopes=settings.required_scopes,
|
| 110 |
)
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
| 117 |
|
| 118 |
-
def
|
| 119 |
-
"""
|
| 120 |
|
| 121 |
-
This
|
| 122 |
-
|
| 123 |
-
- /.well-known/oauth-protected-resource (returns FastMCP resource info)
|
| 124 |
"""
|
|
|
|
|
|
|
| 125 |
|
| 126 |
async def oauth_authorization_server_metadata(request):
|
| 127 |
"""Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
|
|
@@ -142,29 +139,13 @@ class AuthKitProvider(AuthProvider):
|
|
| 142 |
status_code=500,
|
| 143 |
)
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
"bearer_methods_supported": ["header"],
|
| 152 |
-
}
|
| 153 |
)
|
| 154 |
-
|
| 155 |
-
routes.extend(
|
| 156 |
-
[
|
| 157 |
-
Route(
|
| 158 |
-
"/.well-known/oauth-authorization-server",
|
| 159 |
-
endpoint=oauth_authorization_server_metadata,
|
| 160 |
-
methods=["GET"],
|
| 161 |
-
),
|
| 162 |
-
Route(
|
| 163 |
-
"/.well-known/oauth-protected-resource",
|
| 164 |
-
endpoint=oauth_protected_resource_metadata,
|
| 165 |
-
methods=["GET"],
|
| 166 |
-
),
|
| 167 |
-
]
|
| 168 |
)
|
| 169 |
|
| 170 |
return routes
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import httpx
|
|
|
|
|
|
|
|
|
|
| 4 |
from pydantic import AnyHttpUrl
|
| 5 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
from starlette.responses import JSONResponse
|
| 7 |
+
from starlette.routing import Route
|
| 8 |
|
| 9 |
+
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
| 10 |
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
| 11 |
from fastmcp.server.auth.registry import register_provider
|
| 12 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 28 |
|
| 29 |
|
| 30 |
@register_provider("AUTHKIT")
|
| 31 |
+
class AuthKitProvider(RemoteAuthProvider):
|
| 32 |
"""AuthKit metadata provider for DCR (Dynamic Client Registration).
|
| 33 |
|
| 34 |
This provider implements AuthKit integration using metadata forwarding
|
|
|
|
| 80 |
required_scopes: Optional list of scopes to require for all requests
|
| 81 |
token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit
|
| 82 |
"""
|
|
|
|
|
|
|
| 83 |
settings = AuthKitProviderSettings.model_validate(
|
| 84 |
{
|
| 85 |
k: v
|
|
|
|
| 104 |
required_scopes=settings.required_scopes,
|
| 105 |
)
|
| 106 |
|
| 107 |
+
# Initialize RemoteAuthProvider with AuthKit as the authorization server
|
| 108 |
+
super().__init__(
|
| 109 |
+
token_verifier=token_verifier,
|
| 110 |
+
authorization_servers=[AnyHttpUrl(self.authkit_domain)],
|
| 111 |
+
resource_server_url=self.base_url,
|
| 112 |
+
)
|
| 113 |
|
| 114 |
+
def get_routes(self) -> list[Route]:
|
| 115 |
+
"""Get OAuth routes including AuthKit authorization server metadata forwarding.
|
| 116 |
|
| 117 |
+
This returns the standard protected resource routes plus an authorization server
|
| 118 |
+
metadata endpoint that forwards AuthKit's OAuth metadata to clients.
|
|
|
|
| 119 |
"""
|
| 120 |
+
# Get the standard protected resource routes from RemoteAuthProvider
|
| 121 |
+
routes = super().get_routes()
|
| 122 |
|
| 123 |
async def oauth_authorization_server_metadata(request):
|
| 124 |
"""Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
|
|
|
|
| 139 |
status_code=500,
|
| 140 |
)
|
| 141 |
|
| 142 |
+
# Add AuthKit authorization server metadata forwarding
|
| 143 |
+
routes.append(
|
| 144 |
+
Route(
|
| 145 |
+
"/.well-known/oauth-authorization-server",
|
| 146 |
+
endpoint=oauth_authorization_server_metadata,
|
| 147 |
+
methods=["GET"],
|
|
|
|
|
|
|
| 148 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
)
|
| 150 |
|
| 151 |
return routes
|
src/fastmcp/server/http.py
CHANGED
|
@@ -11,12 +11,10 @@ from mcp.server.auth.middleware.bearer_auth import (
|
|
| 11 |
RequireAuthMiddleware,
|
| 12 |
)
|
| 13 |
from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
|
| 14 |
-
from mcp.server.auth.routes import create_auth_routes
|
| 15 |
from mcp.server.lowlevel.server import LifespanResultT
|
| 16 |
from mcp.server.sse import SseServerTransport
|
| 17 |
from mcp.server.streamable_http import EventStore
|
| 18 |
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
| 19 |
-
from pydantic import AnyHttpUrl
|
| 20 |
from starlette.applications import Starlette
|
| 21 |
from starlette.middleware import Middleware
|
| 22 |
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
@@ -25,7 +23,7 @@ from starlette.responses import Response
|
|
| 25 |
from starlette.routing import BaseRoute, Mount, Route
|
| 26 |
from starlette.types import Lifespan, Receive, Scope, Send
|
| 27 |
|
| 28 |
-
from fastmcp.server.auth.auth import AuthProvider
|
| 29 |
from fastmcp.utilities.logging import get_logger
|
| 30 |
|
| 31 |
if TYPE_CHECKING:
|
|
@@ -71,51 +69,6 @@ class RequestContextMiddleware:
|
|
| 71 |
await self.app(scope, receive, send)
|
| 72 |
|
| 73 |
|
| 74 |
-
def setup_auth_middleware_and_routes(
|
| 75 |
-
auth: AuthProvider,
|
| 76 |
-
) -> tuple[list[Middleware], list[Route], list[str]]:
|
| 77 |
-
"""Set up authentication middleware and routes if auth is enabled.
|
| 78 |
-
|
| 79 |
-
Args:
|
| 80 |
-
auth: An AuthProvider for authentication (TokenVerifier or OAuthProvider)
|
| 81 |
-
|
| 82 |
-
Returns:
|
| 83 |
-
Tuple of (middleware, auth_routes, required_scopes)
|
| 84 |
-
"""
|
| 85 |
-
middleware: list[Middleware] = [
|
| 86 |
-
Middleware(
|
| 87 |
-
AuthenticationMiddleware,
|
| 88 |
-
backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
|
| 89 |
-
),
|
| 90 |
-
Middleware(AuthContextMiddleware),
|
| 91 |
-
]
|
| 92 |
-
|
| 93 |
-
auth_routes: list[Route] = []
|
| 94 |
-
required_scopes: list[str] = auth.required_scopes or []
|
| 95 |
-
|
| 96 |
-
# Check if it's an OAuthProvider (has OAuth server capability)
|
| 97 |
-
if isinstance(auth, OAuthProvider):
|
| 98 |
-
# OAuthProvider: create standard OAuth routes first
|
| 99 |
-
standard_routes = list(
|
| 100 |
-
create_auth_routes(
|
| 101 |
-
provider=auth,
|
| 102 |
-
issuer_url=auth.issuer_url,
|
| 103 |
-
service_documentation_url=auth.service_documentation_url,
|
| 104 |
-
client_registration_options=auth.client_registration_options,
|
| 105 |
-
revocation_options=auth.revocation_options,
|
| 106 |
-
)
|
| 107 |
-
)
|
| 108 |
-
|
| 109 |
-
# Allow provider to customize routes (e.g., for proxy behavior or metadata endpoints)
|
| 110 |
-
auth_routes = auth.customize_auth_routes(standard_routes)
|
| 111 |
-
else:
|
| 112 |
-
# Simple AuthProvider or TokenVerifier: start with empty routes
|
| 113 |
-
# Allow provider to add custom routes (e.g., metadata endpoints)
|
| 114 |
-
auth_routes = auth.customize_auth_routes([])
|
| 115 |
-
|
| 116 |
-
return middleware, auth_routes, required_scopes
|
| 117 |
-
|
| 118 |
-
|
| 119 |
def create_base_app(
|
| 120 |
routes: list[BaseRoute],
|
| 121 |
middleware: list[Middleware],
|
|
@@ -183,24 +136,27 @@ def create_sse_app(
|
|
| 183 |
)
|
| 184 |
return Response()
|
| 185 |
|
| 186 |
-
#
|
| 187 |
if auth:
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
server_routes.extend(auth_routes)
|
| 193 |
server_middleware.extend(auth_middleware)
|
| 194 |
|
| 195 |
-
# Determine resource_metadata_url for TokenVerifier
|
| 196 |
-
resource_metadata_url = None
|
| 197 |
-
if isinstance(auth, TokenVerifier) and auth.resource_server_url:
|
| 198 |
-
# Add .well-known path for RFC 9728 compliance
|
| 199 |
-
resource_metadata_url = AnyHttpUrl(
|
| 200 |
-
str(auth.resource_server_url).rstrip("/")
|
| 201 |
-
+ "/.well-known/oauth-protected-resource"
|
| 202 |
-
)
|
| 203 |
-
|
| 204 |
# Auth is enabled, wrap endpoints with RequireAuthMiddleware
|
| 205 |
server_routes.append(
|
| 206 |
Route(
|
|
@@ -328,22 +284,25 @@ def create_streamable_http_app(
|
|
| 328 |
|
| 329 |
# Add StreamableHTTP routes with or without auth
|
| 330 |
if auth:
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
| 335 |
server_routes.extend(auth_routes)
|
| 336 |
server_middleware.extend(auth_middleware)
|
| 337 |
|
| 338 |
-
# Determine resource_metadata_url for TokenVerifier
|
| 339 |
-
resource_metadata_url = None
|
| 340 |
-
if isinstance(auth, TokenVerifier) and auth.resource_server_url:
|
| 341 |
-
# Add .well-known path for RFC 9728 compliance
|
| 342 |
-
resource_metadata_url = AnyHttpUrl(
|
| 343 |
-
str(auth.resource_server_url).rstrip("/")
|
| 344 |
-
+ "/.well-known/oauth-protected-resource"
|
| 345 |
-
)
|
| 346 |
-
|
| 347 |
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
|
| 348 |
server_routes.append(
|
| 349 |
Mount(
|
|
|
|
| 11 |
RequireAuthMiddleware,
|
| 12 |
)
|
| 13 |
from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
|
|
|
|
| 14 |
from mcp.server.lowlevel.server import LifespanResultT
|
| 15 |
from mcp.server.sse import SseServerTransport
|
| 16 |
from mcp.server.streamable_http import EventStore
|
| 17 |
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
|
|
| 18 |
from starlette.applications import Starlette
|
| 19 |
from starlette.middleware import Middleware
|
| 20 |
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
|
|
| 23 |
from starlette.routing import BaseRoute, Mount, Route
|
| 24 |
from starlette.types import Lifespan, Receive, Scope, Send
|
| 25 |
|
| 26 |
+
from fastmcp.server.auth.auth import AuthProvider
|
| 27 |
from fastmcp.utilities.logging import get_logger
|
| 28 |
|
| 29 |
if TYPE_CHECKING:
|
|
|
|
| 69 |
await self.app(scope, receive, send)
|
| 70 |
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
def create_base_app(
|
| 73 |
routes: list[BaseRoute],
|
| 74 |
middleware: list[Middleware],
|
|
|
|
| 136 |
)
|
| 137 |
return Response()
|
| 138 |
|
| 139 |
+
# Set up auth if enabled
|
| 140 |
if auth:
|
| 141 |
+
# Create auth middleware
|
| 142 |
+
auth_middleware = [
|
| 143 |
+
Middleware(
|
| 144 |
+
AuthenticationMiddleware,
|
| 145 |
+
backend=BearerAuthBackend(auth),
|
| 146 |
+
),
|
| 147 |
+
Middleware(AuthContextMiddleware),
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
# Get auth routes and scopes
|
| 151 |
+
auth_routes = auth.get_routes()
|
| 152 |
+
required_scopes = getattr(auth, "required_scopes", None) or []
|
| 153 |
+
|
| 154 |
+
# Get resource metadata URL for WWW-Authenticate header
|
| 155 |
+
resource_metadata_url = auth.get_resource_metadata_url()
|
| 156 |
|
| 157 |
server_routes.extend(auth_routes)
|
| 158 |
server_middleware.extend(auth_middleware)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
# Auth is enabled, wrap endpoints with RequireAuthMiddleware
|
| 161 |
server_routes.append(
|
| 162 |
Route(
|
|
|
|
| 284 |
|
| 285 |
# Add StreamableHTTP routes with or without auth
|
| 286 |
if auth:
|
| 287 |
+
# Create auth middleware
|
| 288 |
+
auth_middleware = [
|
| 289 |
+
Middleware(
|
| 290 |
+
AuthenticationMiddleware,
|
| 291 |
+
backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
|
| 292 |
+
),
|
| 293 |
+
Middleware(AuthContextMiddleware),
|
| 294 |
+
]
|
| 295 |
+
|
| 296 |
+
# Get auth routes and scopes
|
| 297 |
+
auth_routes = auth.get_routes()
|
| 298 |
+
required_scopes = getattr(auth, "required_scopes", None) or []
|
| 299 |
+
|
| 300 |
+
# Get resource metadata URL for WWW-Authenticate header
|
| 301 |
+
resource_metadata_url = auth.get_resource_metadata_url()
|
| 302 |
|
| 303 |
server_routes.extend(auth_routes)
|
| 304 |
server_middleware.extend(auth_middleware)
|
| 305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
|
| 307 |
server_routes.append(
|
| 308 |
Mount(
|
tests/server/http/test_auth_setup.py
DELETED
|
@@ -1,189 +0,0 @@
|
|
| 1 |
-
"""Tests for authentication setup in HTTP apps."""
|
| 2 |
-
|
| 3 |
-
import pytest
|
| 4 |
-
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
|
| 5 |
-
from mcp.server.auth.provider import AccessToken
|
| 6 |
-
from starlette.middleware import Middleware
|
| 7 |
-
from starlette.middleware.authentication import AuthenticationMiddleware
|
| 8 |
-
|
| 9 |
-
from fastmcp.server.auth.providers.in_memory import InMemoryOAuthProvider
|
| 10 |
-
from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
|
| 11 |
-
from fastmcp.server.http import setup_auth_middleware_and_routes
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class TestSetupAuthMiddlewareAndRoutes:
|
| 15 |
-
"""Test setup_auth_middleware_and_routes with TokenVerifier providers."""
|
| 16 |
-
|
| 17 |
-
@pytest.fixture
|
| 18 |
-
def jwt_verifier(self) -> JWTVerifier:
|
| 19 |
-
"""Create JWTVerifier for testing."""
|
| 20 |
-
key_pair = RSAKeyPair.generate()
|
| 21 |
-
return JWTVerifier(
|
| 22 |
-
public_key=key_pair.public_key,
|
| 23 |
-
issuer="https://test.example.com",
|
| 24 |
-
audience="https://api.example.com",
|
| 25 |
-
required_scopes=["read", "write"],
|
| 26 |
-
)
|
| 27 |
-
|
| 28 |
-
@pytest.fixture
|
| 29 |
-
def in_memory_provider(self) -> InMemoryOAuthProvider:
|
| 30 |
-
"""Create InMemoryOAuthProvider for testing."""
|
| 31 |
-
return InMemoryOAuthProvider(
|
| 32 |
-
base_url="https://test.example.com",
|
| 33 |
-
required_scopes=["user"],
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
def test_setup_with_jwt_verifier(self, jwt_verifier: JWTVerifier):
|
| 37 |
-
"""Test that setup works with JWTVerifier as TokenVerifier."""
|
| 38 |
-
middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
| 39 |
-
jwt_verifier
|
| 40 |
-
)
|
| 41 |
-
|
| 42 |
-
# Should return middleware list
|
| 43 |
-
assert isinstance(middleware, list)
|
| 44 |
-
assert len(middleware) == 2 # AuthenticationMiddleware + AuthContextMiddleware
|
| 45 |
-
|
| 46 |
-
# First middleware should be AuthenticationMiddleware with BearerAuthBackend
|
| 47 |
-
auth_middleware = middleware[0]
|
| 48 |
-
assert isinstance(auth_middleware, Middleware)
|
| 49 |
-
assert auth_middleware.cls == AuthenticationMiddleware
|
| 50 |
-
assert "backend" in auth_middleware.kwargs
|
| 51 |
-
|
| 52 |
-
backend = auth_middleware.kwargs["backend"]
|
| 53 |
-
assert isinstance(backend, BearerAuthBackend)
|
| 54 |
-
assert backend.token_verifier is jwt_verifier # type: ignore[attr-defined]
|
| 55 |
-
|
| 56 |
-
# Should return auth routes
|
| 57 |
-
assert isinstance(auth_routes, list)
|
| 58 |
-
assert len(auth_routes) == 0 # TokenVerifier should not have OAuth routes
|
| 59 |
-
|
| 60 |
-
# Should return required scopes
|
| 61 |
-
assert required_scopes == ["read", "write"]
|
| 62 |
-
|
| 63 |
-
def test_setup_with_in_memory_provider(
|
| 64 |
-
self, in_memory_provider: InMemoryOAuthProvider
|
| 65 |
-
):
|
| 66 |
-
"""Test that setup works with InMemoryOAuthProvider as TokenVerifier."""
|
| 67 |
-
middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
| 68 |
-
in_memory_provider
|
| 69 |
-
)
|
| 70 |
-
|
| 71 |
-
# Should return middleware list
|
| 72 |
-
assert isinstance(middleware, list)
|
| 73 |
-
assert len(middleware) == 2
|
| 74 |
-
|
| 75 |
-
# Backend should use the provider as token verifier
|
| 76 |
-
auth_middleware = middleware[0]
|
| 77 |
-
backend = auth_middleware.kwargs["backend"]
|
| 78 |
-
assert isinstance(backend, BearerAuthBackend)
|
| 79 |
-
assert backend.token_verifier is in_memory_provider # type: ignore[attr-defined]
|
| 80 |
-
|
| 81 |
-
# Should return required scopes
|
| 82 |
-
assert required_scopes == ["user"]
|
| 83 |
-
|
| 84 |
-
def test_setup_preserves_provider_functionality(self, jwt_verifier: JWTVerifier):
|
| 85 |
-
"""Test that setup doesn't break the provider's functionality."""
|
| 86 |
-
# Setup should not modify the provider
|
| 87 |
-
original_issuer = jwt_verifier.issuer
|
| 88 |
-
original_scopes = jwt_verifier.required_scopes
|
| 89 |
-
|
| 90 |
-
middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
| 91 |
-
jwt_verifier
|
| 92 |
-
)
|
| 93 |
-
|
| 94 |
-
# Provider should be unchanged
|
| 95 |
-
assert jwt_verifier.issuer == original_issuer
|
| 96 |
-
assert jwt_verifier.required_scopes == original_scopes
|
| 97 |
-
|
| 98 |
-
# Provider should still work as TokenVerifier
|
| 99 |
-
assert hasattr(jwt_verifier, "verify_token")
|
| 100 |
-
assert callable(jwt_verifier.verify_token)
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
class MockOAuthProvider:
|
| 104 |
-
"""Mock OAuth provider that implements TokenVerifier."""
|
| 105 |
-
|
| 106 |
-
def __init__(self, required_scopes=None, issuer_url="http://localhost:8000"):
|
| 107 |
-
from pydantic import AnyHttpUrl
|
| 108 |
-
|
| 109 |
-
from fastmcp.server.auth.auth import (
|
| 110 |
-
ClientRegistrationOptions,
|
| 111 |
-
RevocationOptions,
|
| 112 |
-
)
|
| 113 |
-
|
| 114 |
-
self.required_scopes = required_scopes or []
|
| 115 |
-
self.issuer_url = AnyHttpUrl(issuer_url)
|
| 116 |
-
self.service_documentation_url = None
|
| 117 |
-
self.client_registration_options = ClientRegistrationOptions(enabled=False)
|
| 118 |
-
self.revocation_options = RevocationOptions(enabled=False)
|
| 119 |
-
|
| 120 |
-
async def verify_token(self, token: str) -> AccessToken | None:
|
| 121 |
-
"""Mock verify_token implementation."""
|
| 122 |
-
if token == "valid-token":
|
| 123 |
-
return AccessToken(
|
| 124 |
-
token=token,
|
| 125 |
-
client_id="mock-client",
|
| 126 |
-
scopes=self.required_scopes,
|
| 127 |
-
expires_at=None,
|
| 128 |
-
)
|
| 129 |
-
return None
|
| 130 |
-
|
| 131 |
-
def customize_auth_routes(self, routes):
|
| 132 |
-
"""Mock customize_auth_routes implementation."""
|
| 133 |
-
return routes
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
class TestSetupWithMockProvider:
|
| 137 |
-
"""Test setup function with mock provider."""
|
| 138 |
-
|
| 139 |
-
def test_setup_with_mock_token_verifier(self):
|
| 140 |
-
"""Test that setup works with any TokenVerifier implementation."""
|
| 141 |
-
mock_provider = MockOAuthProvider(required_scopes=["mock-scope"])
|
| 142 |
-
|
| 143 |
-
middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
| 144 |
-
mock_provider # type: ignore[arg-type]
|
| 145 |
-
)
|
| 146 |
-
|
| 147 |
-
# Should work with any TokenVerifier
|
| 148 |
-
assert len(middleware) == 2
|
| 149 |
-
auth_middleware = middleware[0]
|
| 150 |
-
backend = auth_middleware.kwargs["backend"]
|
| 151 |
-
assert isinstance(backend, BearerAuthBackend)
|
| 152 |
-
assert backend.token_verifier is mock_provider # type: ignore[attr-defined]
|
| 153 |
-
|
| 154 |
-
assert required_scopes == ["mock-scope"]
|
| 155 |
-
|
| 156 |
-
async def test_setup_middleware_can_authenticate(self):
|
| 157 |
-
"""Test that the setup middleware can actually authenticate requests."""
|
| 158 |
-
mock_provider = MockOAuthProvider()
|
| 159 |
-
|
| 160 |
-
middleware, _, _ = setup_auth_middleware_and_routes(mock_provider) # type: ignore[arg-type]
|
| 161 |
-
|
| 162 |
-
# Extract the BearerAuthBackend
|
| 163 |
-
auth_middleware = middleware[0]
|
| 164 |
-
backend = auth_middleware.kwargs["backend"]
|
| 165 |
-
|
| 166 |
-
# Test authentication with valid token
|
| 167 |
-
from starlette.requests import HTTPConnection
|
| 168 |
-
|
| 169 |
-
scope = {
|
| 170 |
-
"type": "http",
|
| 171 |
-
"headers": [(b"authorization", b"Bearer valid-token")],
|
| 172 |
-
}
|
| 173 |
-
conn = HTTPConnection(scope)
|
| 174 |
-
|
| 175 |
-
result = await backend.authenticate(conn) # type: ignore[attr-defined]
|
| 176 |
-
assert result is not None
|
| 177 |
-
|
| 178 |
-
credentials, user = result
|
| 179 |
-
assert user.username == "mock-client"
|
| 180 |
-
|
| 181 |
-
# Test authentication with invalid token
|
| 182 |
-
scope = {
|
| 183 |
-
"type": "http",
|
| 184 |
-
"headers": [(b"authorization", b"Bearer invalid-token")],
|
| 185 |
-
}
|
| 186 |
-
conn = HTTPConnection(scope)
|
| 187 |
-
|
| 188 |
-
result = await backend.authenticate(conn) # type: ignore[attr-defined]
|
| 189 |
-
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|