Rename OAuthProxy -> OAuthDCRProxy

This commit is contained in:
Jeremiah Lowin 2025-10-20 15:41:16 -04:00
commit dd86edf275
17 changed files with 3416 additions and 2075 deletions

View file

@ -3,7 +3,7 @@ title: oauth_proxy
sidebarTitle: oauth_proxy
---
# `fastmcp.server.auth.oauth_proxy`
# `fastmcp.server.auth.oauth_dcr_proxy`
OAuth Proxy Provider for FastMCP.

View file

@ -6,8 +6,10 @@ from .auth import (
AuthProvider,
)
from .providers.jwt import JWTVerifier, StaticTokenVerifier
from .oauth_proxy import OAuthProxy
from .oauth_dcr_proxy import OAuthDCRProxy
import warnings
import fastmcp
__all__ = [
"AuthProvider",
@ -17,7 +19,7 @@ __all__ = [
"StaticTokenVerifier",
"RemoteAuthProvider",
"AccessToken",
"OAuthProxy",
"OAuthDCRProxy",
]
@ -27,4 +29,18 @@ def __getattr__(name: str):
from .providers.bearer import BearerAuthProvider
return BearerAuthProvider
if name == "OAuthProxy":
from .oauth_dcr_proxy import OAuthDCRProxy as OAuthProxy
if fastmcp.settings.deprecation_warnings:
warnings.warn(
"The `OAuthProxy` class is deprecated "
"and has been replaced by `OAuthDCRProxy`. "
"This import will be removed in a future version.",
DeprecationWarning,
stacklevel=2,
)
return OAuthProxy
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ from pydantic import AnyHttpUrl, BaseModel, model_validator
from typing_extensions import Self
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.logging import get_logger
@ -169,7 +169,7 @@ class OIDCConfiguration(BaseModel):
raise
class OIDCProxy(OAuthProxy):
class OIDCProxy(OAuthDCRProxy):
"""OAuth provider that wraps OAuthProxy to provide configuration via an OIDC configuration URL.
This provider makes it easier to add OAuth protection for any upstream provider

View file

@ -12,7 +12,7 @@ from key_value.aio.protocols import AsyncKeyValue
from pydantic import SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
@ -57,7 +57,7 @@ class AzureProviderSettings(BaseSettings):
return parse_scopes(v)
class AzureProvider(OAuthProxy):
class AzureProvider(OAuthDCRProxy):
"""Azure (Microsoft Entra) OAuth provider for FastMCP.
This provider implements Azure/Microsoft Entra ID authentication using the

View file

@ -28,7 +28,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
@ -166,7 +166,7 @@ class GitHubTokenVerifier(TokenVerifier):
return None
class GitHubProvider(OAuthProxy):
class GitHubProvider(OAuthDCRProxy):
"""Complete GitHub OAuth provider for FastMCP.
This provider makes it trivial to add GitHub OAuth protection to any

View file

@ -30,7 +30,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
@ -182,7 +182,7 @@ class GoogleTokenVerifier(TokenVerifier):
return None
class GoogleProvider(OAuthProxy):
class GoogleProvider(OAuthDCRProxy):
"""Complete Google OAuth provider for FastMCP.
This provider makes it trivial to add Google OAuth protection to any

View file

@ -18,7 +18,7 @@ from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
@ -125,7 +125,7 @@ class WorkOSTokenVerifier(TokenVerifier):
return None
class WorkOSProvider(OAuthProxy):
class WorkOSProvider(OAuthDCRProxy):
"""Complete WorkOS OAuth provider for FastMCP.
This provider implements WorkOS AuthKit OAuth using the OAuth Proxy pattern.

View file

@ -82,7 +82,7 @@ def create_github_server_with_mock_callback(base_url: str) -> FastMCP:
import secrets
import time
from fastmcp.server.auth.oauth_proxy import ClientCode
from fastmcp.server.auth.oauth_dcr_proxy import ClientCode
# Generate a fake authorization code
fake_code = secrets.token_urlsafe(32)

View file

@ -25,7 +25,7 @@ from starlette.applications import Starlette
from starlette.testclient import TestClient
from fastmcp.server.auth.auth import TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
class MockTokenVerifier(TokenVerifier):
@ -69,7 +69,7 @@ def storage():
@pytest.fixture
def oauth_proxy_with_storage(storage):
"""Create OAuth proxy with explicit storage backend."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-upstream-client",
@ -84,7 +84,7 @@ def oauth_proxy_with_storage(storage):
@pytest.fixture
def oauth_proxy_https():
"""OAuthProxy configured with HTTPS base_url for __Host- cookies."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="client-id",
@ -96,7 +96,7 @@ def oauth_proxy_https():
async def _start_flow(
proxy: OAuthProxy, client_id: str, redirect: str
proxy: OAuthDCRProxy, client_id: str, redirect: str
) -> tuple[str, str]:
"""Register client and start auth; returns (txn_id, consent_url)."""
await proxy.register_client(
@ -503,7 +503,7 @@ class TestStoragePersistence:
async def test_storage_uses_pydantic_adapter(self, oauth_proxy_with_storage):
"""Verify that PydanticAdapter serializes/deserializes correctly."""
from fastmcp.server.auth.oauth_proxy import OAuthTransaction
from fastmcp.server.auth.oauth_dcr_proxy import OAuthTransaction
client = OAuthClientInformationFull(
client_id="pydantic-test-client",
@ -674,7 +674,7 @@ class TestConsentPageServerIcon:
verifier.verify_token = Mock(return_value=None)
# Create OAuthProxy
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -705,7 +705,7 @@ class TestConsentPageServerIcon:
await proxy.register_client(client_info)
# Create a transaction manually
from fastmcp.server.auth.oauth_proxy import OAuthTransaction
from fastmcp.server.auth.oauth_dcr_proxy import OAuthTransaction
txn_id = "test-txn-id"
transaction = OAuthTransaction(
@ -745,7 +745,7 @@ class TestConsentPageServerIcon:
verifier.verify_token = Mock(return_value=None)
# Create OAuthProxy
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -769,7 +769,7 @@ class TestConsentPageServerIcon:
await proxy.register_client(client_info)
# Create a transaction
from fastmcp.server.auth.oauth_proxy import OAuthTransaction
from fastmcp.server.auth.oauth_dcr_proxy import OAuthTransaction
txn_id = "test-txn-id"
transaction = OAuthTransaction(
@ -811,7 +811,7 @@ class TestConsentPageServerIcon:
verifier.verify_token = Mock(return_value=None)
# Create OAuthProxy
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -839,7 +839,7 @@ class TestConsentPageServerIcon:
await proxy.register_client(client_info)
# Create a transaction
from fastmcp.server.auth.oauth_proxy import OAuthTransaction
from fastmcp.server.auth.oauth_dcr_proxy import OAuthTransaction
txn_id = "test-txn-id"
transaction = OAuthTransaction(

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ from mcp.shared.auth import InvalidRedirectUriError
from pydantic import AnyUrl
from fastmcp.server.auth.auth import TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy, ProxyDCRClient
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy, ProxyDCRClient
class MockTokenVerifier(TokenVerifier):
@ -103,7 +103,7 @@ class TestOAuthProxyRedirectValidation:
def test_proxy_default_allows_all(self):
"""Test that OAuth proxy defaults to allowing all URIs for DCR compatibility."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client",
@ -119,7 +119,7 @@ class TestOAuthProxyRedirectValidation:
"""Test OAuth proxy with custom redirect patterns."""
custom_patterns = ["http://localhost:*", "https://*.myapp.com/*"]
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client",
@ -133,7 +133,7 @@ class TestOAuthProxyRedirectValidation:
def test_proxy_empty_list_validation(self):
"""Test OAuth proxy with empty list (allow none)."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client",
@ -149,7 +149,7 @@ class TestOAuthProxyRedirectValidation:
"""Test that registered clients use the configured patterns."""
custom_patterns = ["https://app.example.com/*"]
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client",
@ -181,7 +181,7 @@ class TestOAuthProxyRedirectValidation:
"""Test that unregistered clients return None."""
custom_patterns = ["http://localhost:*", "http://127.0.0.1:*"]
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client",

View file

@ -12,7 +12,7 @@ from key_value.aio.stores.memory import MemoryStore
from mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyUrl
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
class TestOAuthProxyStorage:
@ -39,9 +39,9 @@ class TestOAuthProxyStorage:
"""Create in-memory storage for testing."""
return MemoryStore()
def create_proxy(self, jwt_verifier, storage=None) -> OAuthProxy:
def create_proxy(self, jwt_verifier, storage=None) -> OAuthDCRProxy:
"""Create an OAuth proxy with specified storage."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-client-id",
@ -109,7 +109,7 @@ class TestOAuthProxyStorage:
self, jwt_verifier, temp_storage
):
"""Test that ProxyDCRClient is created with redirect URI patterns."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-client-id",

View file

@ -27,7 +27,7 @@ from starlette.routing import Route
from fastmcp import FastMCP
from fastmcp.server.auth.auth import AccessToken, RefreshToken, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
# =============================================================================
@ -311,7 +311,7 @@ def jwt_verifier():
@pytest.fixture
def oauth_proxy(jwt_verifier):
"""Create a standard OAuthProxy instance for testing."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-client-id",
@ -341,7 +341,7 @@ class TestOAuthProxyInitialization:
def test_basic_initialization(self, jwt_verifier):
"""Test basic proxy initialization with required parameters."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="client-123",
@ -361,7 +361,7 @@ class TestOAuthProxyInitialization:
def test_all_optional_parameters(self, jwt_verifier):
"""Test initialization with all optional parameters."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="client-123",
@ -387,7 +387,7 @@ class TestOAuthProxyInitialization:
def test_redirect_path_normalization(self, jwt_verifier):
"""Test that redirect_path is normalized with leading slash."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.com/authorize",
upstream_token_endpoint="https://auth.com/token",
upstream_client_id="client",
@ -485,7 +485,7 @@ class TestOAuthProxyPKCE:
@pytest.fixture
def proxy_with_pkce(self, jwt_verifier):
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -497,7 +497,7 @@ class TestOAuthProxyPKCE:
@pytest.fixture
def proxy_without_pkce(self, jwt_verifier):
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -583,7 +583,7 @@ class TestOAuthProxyTokenEndpointAuth:
def test_token_auth_method_initialization(self, jwt_verifier):
"""Test different token endpoint auth methods."""
# client_secret_post
proxy_post = OAuthProxy(
proxy_post = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="client",
@ -595,7 +595,7 @@ class TestOAuthProxyTokenEndpointAuth:
assert proxy_post._token_endpoint_auth_method == "client_secret_post"
# client_secret_basic (default)
proxy_basic = OAuthProxy(
proxy_basic = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="client",
@ -607,7 +607,7 @@ class TestOAuthProxyTokenEndpointAuth:
assert proxy_basic._token_endpoint_auth_method == "client_secret_basic"
# None (use authlib default)
proxy_default = OAuthProxy(
proxy_default = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="client",
@ -619,7 +619,7 @@ class TestOAuthProxyTokenEndpointAuth:
async def test_token_auth_method_passed_to_client(self, jwt_verifier):
"""Test that auth method is passed to AsyncOAuth2Client."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="client-id",
@ -637,7 +637,9 @@ class TestOAuthProxyTokenEndpointAuth:
)
# Mock the upstream OAuth provider response
with patch("fastmcp.server.auth.oauth_proxy.AsyncOAuth2Client") as MockClient:
with patch(
"fastmcp.server.auth.oauth_dcr_proxy.AsyncOAuth2Client"
) as MockClient:
mock_client = AsyncMock()
# Mock initial token exchange (authorization code flow)
@ -665,7 +667,7 @@ class TestOAuthProxyTokenEndpointAuth:
await proxy.register_client(client)
# Store client code that would be created during OAuth callback
from fastmcp.server.auth.oauth_proxy import ClientCode
from fastmcp.server.auth.oauth_dcr_proxy import ClientCode
client_code = ClientCode(
code="test-auth-code",
@ -740,7 +742,7 @@ class TestOAuthProxyE2E:
async def test_full_oauth_flow_with_mock_provider(self, mock_oauth_provider):
"""Test complete OAuth flow with mock provider."""
# Create proxy pointing to mock provider
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint=mock_oauth_provider.authorize_endpoint,
upstream_token_endpoint=mock_oauth_provider.token_endpoint,
upstream_client_id="mock-client",
@ -793,7 +795,7 @@ class TestOAuthProxyE2E:
async def test_token_refresh_with_mock_provider(self, mock_oauth_provider):
"""Test token refresh flow with mock provider."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint=mock_oauth_provider.authorize_endpoint,
upstream_token_endpoint=mock_oauth_provider.token_endpoint,
upstream_client_id="mock-client",
@ -818,7 +820,9 @@ class TestOAuthProxyE2E:
"scope": "read write",
}
with patch("fastmcp.server.auth.oauth_proxy.AsyncOAuth2Client") as MockClient:
with patch(
"fastmcp.server.auth.oauth_dcr_proxy.AsyncOAuth2Client"
) as MockClient:
mock_client = AsyncMock()
# Mock initial token exchange to get FastMCP tokens
@ -847,7 +851,7 @@ class TestOAuthProxyE2E:
MockClient.return_value = mock_client
# Store client code that would be created during OAuth callback
from fastmcp.server.auth.oauth_proxy import ClientCode
from fastmcp.server.auth.oauth_dcr_proxy import ClientCode
client_code = ClientCode(
code="test-auth-code",
@ -907,7 +911,7 @@ class TestOAuthProxyE2E:
"""Test PKCE validation with mock provider."""
mock_oauth_provider.require_pkce = True
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint=mock_oauth_provider.authorize_endpoint,
upstream_token_endpoint=mock_oauth_provider.token_endpoint,
upstream_client_id="mock-client",
@ -961,7 +965,7 @@ class TestParameterForwarding:
@pytest.fixture
def proxy_with_extra_params(self, jwt_verifier):
"""Create OAuthProxy with extra parameters configured."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -975,7 +979,7 @@ class TestParameterForwarding:
@pytest.fixture
def proxy_without_extra_params(self, jwt_verifier):
"""Create OAuthProxy without extra parameters."""
return OAuthProxy(
return OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -1121,7 +1125,7 @@ class TestParameterForwarding:
async def test_multiple_extra_params(self, jwt_verifier):
"""Test multiple extra parameters can be configured and forwarded."""
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -1182,7 +1186,7 @@ class TestParameterForwarding:
from starlette.applications import Starlette
from starlette.testclient import TestClient
proxy = OAuthProxy(
proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://oauth.example.com/authorize",
upstream_token_endpoint="https://oauth.example.com/token",
upstream_client_id="upstream-client",
@ -1233,7 +1237,7 @@ class TestTokenHandlerErrorTransformation:
"""Test that client authentication failures return invalid_client with 401."""
from mcp.server.auth.handlers.token import TokenErrorResponse
from fastmcp.server.auth.oauth_proxy import TokenHandler
from fastmcp.server.auth.oauth_dcr_proxy import TokenHandler
handler = TokenHandler(provider=Mock(), client_authenticator=Mock())
@ -1257,7 +1261,7 @@ class TestTokenHandlerErrorTransformation:
"""Test that grant type authorization errors stay as unauthorized_client with 400."""
from mcp.server.auth.handlers.token import TokenErrorResponse
from fastmcp.server.auth.oauth_proxy import TokenHandler
from fastmcp.server.auth.oauth_dcr_proxy import TokenHandler
handler = TokenHandler(provider=Mock(), client_authenticator=Mock())
@ -1277,7 +1281,7 @@ class TestTokenHandlerErrorTransformation:
"""Test that other error types pass through unchanged."""
from mcp.server.auth.handlers.token import TokenErrorResponse
from fastmcp.server.auth.oauth_proxy import TokenHandler
from fastmcp.server.auth.oauth_dcr_proxy import TokenHandler
handler = TokenHandler(provider=Mock(), client_authenticator=Mock())