docs/python-sdk/fastmcp-server-auth-oauth_proxy-proxy.mdx
fastmcp.server.auth.oauth_proxy.proxyOAuth 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:
This implementation is based on the OAuth 2.1 specification and is designed for production use with enterprise identity providers.
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.
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:
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.
Dynamic Client Registration:
Dynamic Redirect URIs:
Authorization Code Mapping:
State Parameter Collision:
Token Management:
Client Registration (DCR):
Authorization:
Upstream Callback:
Token Exchange:
Token Refresh:
The proxy maintains minimal but crucial state via pluggable storage (client_storage):
All state is stored in the configured client_storage backend (Redis, disk, etc.) enabling horizontal scaling across multiple instances.
Works with any OAuth 2.0 provider that supports:
Handles provider-specific requirements:
Methods:
set_mcp_path <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L576" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L600" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L660" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L704" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L757" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
Start OAuth transaction and route through consent interstitial.
Flow:
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#L876" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L924" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
Exchange authorization code for FastMCP-issued tokens.
Implements the token factory pattern:
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#L1182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L1211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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:
load_access_token <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1562" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>load_access_token(self, token: str) -> AccessToken | None
Validate FastMCP JWT by swapping for upstream token.
This implements the token swap pattern:
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#L1727" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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#L1773" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>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:
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
This is used to advertise the resource URL in metadata.