mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
324 lines
13 KiB
Text
324 lines
13 KiB
Text
---
|
|
title: proxy
|
|
sidebarTitle: proxy
|
|
---
|
|
|
|
# `fastmcp.server.auth.oauth_proxy.proxy`
|
|
|
|
|
|
OAuth Proxy Provider for FastMCP.
|
|
|
|
This provider acts as a transparent proxy to an upstream OAuth Authorization Server,
|
|
handling Dynamic Client Registration locally while forwarding all other OAuth flows.
|
|
This enables authentication with upstream providers that don't support DCR or have
|
|
restricted client registration policies.
|
|
|
|
Key features:
|
|
- Proxies authorization and token endpoints to upstream server
|
|
- Implements local Dynamic Client Registration with fixed upstream credentials
|
|
- Validates tokens using upstream JWKS
|
|
- Maintains minimal local state for bookkeeping
|
|
- Enhanced logging with request correlation
|
|
|
|
This implementation is based on the OAuth 2.1 specification and is designed for
|
|
production use with enterprise identity providers.
|
|
|
|
|
|
## Classes
|
|
|
|
### `OAuthProxy` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
|
|
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
|
|
|
|
Purpose
|
|
-------
|
|
MCP clients expect OAuth providers to support Dynamic Client Registration (DCR),
|
|
where clients can register themselves dynamically and receive unique credentials.
|
|
Most enterprise IDPs (Google, GitHub, Azure AD, etc.) don't support DCR and require
|
|
pre-registered OAuth applications with fixed credentials.
|
|
|
|
This proxy bridges that gap by:
|
|
- Presenting a full DCR-compliant OAuth interface to MCP clients
|
|
- Translating DCR registration requests to use pre-configured upstream credentials
|
|
- Proxying all OAuth flows to the upstream IDP with appropriate translations
|
|
- Managing the state and security requirements of both protocols
|
|
|
|
Architecture Overview
|
|
--------------------
|
|
The proxy maintains a single OAuth app registration with the upstream provider
|
|
while allowing unlimited MCP clients to register and authenticate dynamically.
|
|
It implements the complete OAuth 2.1 + DCR specification for clients while
|
|
translating to whatever OAuth variant the upstream provider requires.
|
|
|
|
Key Translation Challenges Solved
|
|
---------------------------------
|
|
1. Dynamic Client Registration:
|
|
- MCP clients expect to register dynamically and get unique credentials
|
|
- Upstream IDPs require pre-registered apps with fixed credentials
|
|
- Solution: Accept DCR requests, return shared upstream credentials
|
|
|
|
2. Dynamic Redirect URIs:
|
|
- MCP clients use random localhost ports that change between sessions
|
|
- Upstream IDPs require fixed, pre-registered redirect URIs
|
|
- Solution: Use proxy's fixed callback URL with upstream, forward to client's dynamic URI
|
|
|
|
3. Authorization Code Mapping:
|
|
- Upstream returns codes for the proxy's redirect URI
|
|
- Clients expect codes for their own redirect URIs
|
|
- Solution: Exchange upstream code server-side, issue new code to client
|
|
|
|
4. State Parameter Collision:
|
|
- Both client and proxy need to maintain state through the flow
|
|
- Only one state parameter available in OAuth
|
|
- Solution: Use transaction ID as state with upstream, preserve client's state
|
|
|
|
5. Token Management:
|
|
- Clients may expect different token formats/claims than upstream provides
|
|
- Need to track tokens for revocation and refresh
|
|
- Solution: Store token relationships, forward upstream tokens transparently
|
|
|
|
OAuth Flow Implementation
|
|
------------------------
|
|
1. Client Registration (DCR):
|
|
- Accept any client registration request
|
|
- Store ProxyDCRClient that accepts dynamic redirect URIs
|
|
|
|
2. Authorization:
|
|
- Store transaction mapping client details to proxy flow
|
|
- Redirect to upstream with proxy's fixed redirect URI
|
|
- Use transaction ID as state parameter with upstream
|
|
|
|
3. Upstream Callback:
|
|
- Exchange upstream authorization code for tokens (server-side)
|
|
- Generate new authorization code bound to client's PKCE challenge
|
|
- Redirect to client's original dynamic redirect URI
|
|
|
|
4. Token Exchange:
|
|
- Validate client's code and PKCE verifier
|
|
- Return previously obtained upstream tokens
|
|
- Clean up one-time use authorization code
|
|
|
|
5. Token Refresh:
|
|
- Forward refresh requests to upstream using authlib
|
|
- Handle token rotation if upstream issues new refresh token
|
|
- Update local token mappings
|
|
|
|
State Management
|
|
---------------
|
|
The proxy maintains minimal but crucial state via pluggable storage (client_storage):
|
|
- _oauth_transactions: Active authorization flows with client context
|
|
- _client_codes: Authorization codes with PKCE challenges and upstream tokens
|
|
- _jti_mapping_store: Maps FastMCP token JTIs to upstream token IDs
|
|
- _refresh_token_store: Refresh token metadata (keyed by token hash)
|
|
|
|
All state is stored in the configured client_storage backend (Redis, disk, etc.)
|
|
enabling horizontal scaling across multiple instances.
|
|
|
|
Security Considerations
|
|
----------------------
|
|
- Refresh tokens stored by hash only (defense in depth if storage compromised)
|
|
- PKCE enforced end-to-end (client to proxy, proxy to upstream)
|
|
- Authorization codes are single-use with short expiry
|
|
- Transaction IDs are cryptographically random
|
|
- All state is cleaned up after use to prevent replay
|
|
- Token validation delegates to upstream provider
|
|
|
|
Provider Compatibility
|
|
---------------------
|
|
Works with any OAuth 2.0 provider that supports:
|
|
- Authorization code flow
|
|
- Fixed redirect URI (configured in provider's app settings)
|
|
- Standard token endpoint
|
|
|
|
Handles provider-specific requirements:
|
|
- Google: Ensures minimum scope requirements
|
|
- GitHub: Compatible with OAuth Apps and GitHub Apps
|
|
- Azure AD: Handles tenant-specific endpoints
|
|
- Generic: Works with any spec-compliant provider
|
|
|
|
|
|
**Methods:**
|
|
|
|
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L570" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
set_mcp_path(self, mcp_path: str | None) -> None
|
|
```
|
|
|
|
Set the MCP endpoint path and create JWTIssuer with correct audience.
|
|
|
|
This method is called by get_routes() to configure the resource URL
|
|
and create the JWTIssuer. The JWT audience is set to the full resource
|
|
URL (e.g., http://localhost:8000/mcp) to ensure tokens are bound to
|
|
this specific MCP endpoint.
|
|
|
|
**Args:**
|
|
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
|
|
|
|
|
#### `jwt_issuer` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L594" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
jwt_issuer(self) -> JWTIssuer
|
|
```
|
|
|
|
Get the JWT issuer, ensuring it has been initialized.
|
|
|
|
The JWT issuer is created when set_mcp_path() is called (via get_routes()).
|
|
This property ensures a clear error if used before initialization.
|
|
|
|
|
|
#### `get_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L654" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
|
```
|
|
|
|
Get client information by ID. This is generally the random ID
|
|
provided to the DCR client during registration, not the upstream client ID.
|
|
|
|
For unregistered clients, returns None (which will raise an error in the SDK).
|
|
CIMD clients (URL-based client IDs) are looked up and cached automatically.
|
|
|
|
|
|
#### `register_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L698" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
register_client(self, client_info: OAuthClientInformationFull) -> None
|
|
```
|
|
|
|
Register a client locally
|
|
|
|
When a client registers, we create a ProxyDCRClient that is more
|
|
forgiving about validating redirect URIs, since the DCR client's
|
|
redirect URI will likely be localhost or unknown to the proxied IDP. The
|
|
proxied IDP only knows about this server's fixed redirect URI.
|
|
|
|
|
|
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L751" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
|
```
|
|
|
|
Start OAuth transaction and route through consent interstitial.
|
|
|
|
Flow:
|
|
1. Validate client's resource matches server's resource URL (security check)
|
|
2. Store transaction with client details and PKCE (if forwarding)
|
|
3. Return local /consent URL; browser visits consent first
|
|
4. Consent handler redirects to upstream IdP if approved/already approved
|
|
|
|
If consent is disabled (require_authorization_consent=False), skip the consent screen
|
|
and redirect directly to the upstream IdP.
|
|
|
|
|
|
#### `load_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L870" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
|
```
|
|
|
|
Load authorization code for validation.
|
|
|
|
Look up our client code and return authorization code object
|
|
with PKCE challenge for validation.
|
|
|
|
|
|
#### `exchange_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L918" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
|
```
|
|
|
|
Exchange authorization code for FastMCP-issued tokens.
|
|
|
|
Implements the token factory pattern:
|
|
1. Retrieves upstream tokens from stored authorization code
|
|
2. Extracts user identity from upstream token
|
|
3. Encrypts and stores upstream tokens
|
|
4. Issues FastMCP-signed JWT tokens
|
|
5. Returns FastMCP tokens (NOT upstream tokens)
|
|
|
|
PKCE validation is handled by the MCP framework before this method is called.
|
|
|
|
|
|
#### `load_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1176" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
|
```
|
|
|
|
Load refresh token metadata from distributed storage.
|
|
|
|
Looks up by token hash and reconstructs the RefreshToken object.
|
|
Validates that the token belongs to the requesting client.
|
|
|
|
|
|
#### `exchange_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
|
```
|
|
|
|
Exchange FastMCP refresh token for new FastMCP access token.
|
|
|
|
Implements two-tier refresh:
|
|
1. Verify FastMCP refresh token
|
|
2. Look up upstream token via JTI mapping
|
|
3. Refresh upstream token with upstream provider
|
|
4. Update stored upstream token
|
|
5. Issue new FastMCP access token
|
|
6. Keep same FastMCP refresh token (unless upstream rotates)
|
|
|
|
|
|
#### `load_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1556" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
load_access_token(self, token: str) -> AccessToken | None
|
|
```
|
|
|
|
Validate FastMCP JWT by swapping for upstream token.
|
|
|
|
This implements the token swap pattern:
|
|
1. Verify FastMCP JWT signature (proves it's our token)
|
|
2. Look up upstream token via JTI mapping
|
|
3. Decrypt upstream token
|
|
4. Validate upstream token with provider (GitHub API, JWT validation, etc.)
|
|
5. If upstream validation fails, attempt transparent refresh
|
|
6. Return upstream validation result
|
|
|
|
The FastMCP JWT is a reference token - all authorization data comes
|
|
from validating the upstream token via the TokenVerifier.
|
|
|
|
|
|
#### `revoke_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1709" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
|
```
|
|
|
|
Revoke token locally and with upstream server if supported.
|
|
|
|
For refresh tokens, removes from local storage by hash.
|
|
For all tokens, attempts upstream revocation if endpoint is configured.
|
|
Access token JTI mappings expire via TTL.
|
|
|
|
|
|
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1755" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
|
|
|
```python
|
|
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
|
```
|
|
|
|
Get OAuth routes with custom handlers for better error UX.
|
|
|
|
This method creates standard OAuth routes and replaces:
|
|
- /authorize endpoint: Enhanced error responses for unregistered clients
|
|
- /token endpoint: OAuth 2.1 compliant error codes
|
|
|
|
**Args:**
|
|
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
|
This is used to advertise the resource URL in metadata.
|
|
|