Rename OAuth providers to include DCR suffix

Renames all OAuth providers that inherit from OAuthDCRProxy to explicitly
include "DCR" in their names, clarifying their Dynamic Client Registration
implementation approach.

Changes:
- GitHubProvider → GitHubDCRProvider
- GoogleProvider → GoogleDCRProvider
- AzureProvider → AzureDCRProvider
- WorkOSProvider → WorkOSDCRProvider
- Auth0Provider → Auth0DCRProvider
- AWSCognitoProvider → AWSCognitoDCRProvider

All old names remain as deprecated aliases with warnings that respect
settings.deprecation_warnings. Environment variables updated to include
_DCR_ with backwards compatibility via env_prefixes.
This commit is contained in:
Jeremiah Lowin 2025-10-20 17:32:35 -04:00
commit 0ad638003c
13 changed files with 676 additions and 342 deletions

View file

@ -6,10 +6,10 @@ just the configuration URL, client ID, client secret, audience, and base URL.
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider
from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# Simple Auth0 OAuth protection
auth = Auth0Provider(
auth = Auth0DCRProvider(
config_url="https://auth0.config.url",
client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret",
@ -21,12 +21,19 @@ Example:
```
"""
import warnings
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
from fastmcp.settings import ENV_FILE
from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -34,15 +41,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
class Auth0ProviderSettings(BaseSettings):
"""Settings for Auth0 OIDC provider."""
class Auth0DCRProviderSettings(BaseSettings):
"""Settings for Auth0 OIDC DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AUTH0_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AUTH0_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_AUTH0_DCR_", "FASTMCP_SERVER_AUTH_AUTH0_"],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
config_url: AnyHttpUrl | None = None
client_id: str | None = None
client_secret: SecretStr | None = None
@ -59,8 +83,8 @@ class Auth0ProviderSettings(BaseSettings):
return parse_scopes(v)
class Auth0Provider(OIDCDCRProxy):
"""An Auth0 provider implementation for FastMCP.
class Auth0DCRProvider(OIDCDCRProxy):
"""An Auth0 DCR provider implementation for FastMCP.
This provider is a complete Auth0 integration that's ready to use with
just the configuration URL, client ID, client secret, audience, and base URL.
@ -68,10 +92,10 @@ class Auth0Provider(OIDCDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider
from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# Simple Auth0 OAuth protection
auth = Auth0Provider(
auth = Auth0DCRProvider(
config_url="https://auth0.config.url",
client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret",
@ -113,7 +137,7 @@ class Auth0Provider(OIDCDCRProxy):
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = Auth0ProviderSettings.model_validate(
provider_settings = Auth0DCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -131,50 +155,67 @@ class Auth0Provider(OIDCDCRProxy):
}
)
if not settings.config_url:
if not provider_settings.config_url:
raise ValueError(
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL"
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL"
)
if not settings.client_id:
if not provider_settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID"
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID"
)
if not settings.client_secret:
if not provider_settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET"
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET"
)
if not settings.audience:
if not provider_settings.audience:
raise ValueError(
"audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE"
"audience is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE"
)
if not settings.base_url:
if not provider_settings.base_url:
raise ValueError(
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_BASE_URL"
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL"
)
auth0_required_scopes = settings.required_scopes or ["openid"]
auth0_required_scopes = provider_settings.required_scopes or ["openid"]
init_kwargs = {
"config_url": settings.config_url,
"client_id": settings.client_id,
"client_secret": settings.client_secret.get_secret_value(),
"audience": settings.audience,
"base_url": settings.base_url,
"issuer_url": settings.issuer_url,
"redirect_path": settings.redirect_path,
"config_url": provider_settings.config_url,
"client_id": provider_settings.client_id,
"client_secret": provider_settings.client_secret.get_secret_value(),
"audience": provider_settings.audience,
"base_url": provider_settings.base_url,
"issuer_url": provider_settings.issuer_url,
"redirect_path": provider_settings.redirect_path,
"required_scopes": auth0_required_scopes,
"allowed_client_redirect_uris": settings.allowed_client_redirect_uris,
"allowed_client_redirect_uris": provider_settings.allowed_client_redirect_uris,
"client_storage": client_storage,
}
super().__init__(**init_kwargs)
logger.info(
"Initialized Auth0 OAuth provider for client %s with scopes: %s",
settings.client_id,
"Initialized Auth0 OAuth DCR provider for client %s with scopes: %s",
provider_settings.client_id,
auth0_required_scopes,
)
# Deprecated alias for backwards compatibility
class Auth0Provider(Auth0DCRProvider):
"""Deprecated: Use Auth0DCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"Auth0Provider is deprecated, use Auth0DCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)

View file

@ -23,15 +23,22 @@ Example:
from __future__ import annotations
import warnings
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.settings import ENV_FILE
from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -39,15 +46,35 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
class AWSCognitoProviderSettings(BaseSettings):
"""Settings for AWS Cognito OAuth provider."""
class AWSCognitoDCRProviderSettings(BaseSettings):
"""Settings for AWS Cognito OAuth DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_",
env_prefixes=[
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_",
],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
user_pool_id: str | None = None
aws_region: str | None = None
client_id: str | None = None
@ -91,8 +118,8 @@ class AWSCognitoTokenVerifier(JWTVerifier):
)
class AWSCognitoProvider(OIDCDCRProxy):
"""Complete AWS Cognito OAuth provider for FastMCP.
class AWSCognitoDCRProvider(OIDCDCRProxy):
"""Complete AWS Cognito OAuth DCR provider for FastMCP.
This provider makes it trivial to add AWS Cognito OAuth protection to any
FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
@ -107,9 +134,9 @@ class AWSCognitoProvider(OIDCDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.aws_cognito import AWSCognitoProvider
from fastmcp.server.auth.providers.aws_cognito import AWSCognitoDCRProvider
auth = AWSCognitoProvider(
auth = AWSCognitoDCRProvider(
user_pool_id="eu-central-1_XXXXXXXXX",
aws_region="eu-central-1",
client_id="your-cognito-client-id",
@ -153,7 +180,7 @@ class AWSCognitoProvider(OIDCDCRProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = AWSCognitoProviderSettings.model_validate(
provider_settings = AWSCognitoDCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -172,57 +199,78 @@ class AWSCognitoProvider(OIDCDCRProxy):
)
# Validate required settings
if not settings.user_pool_id:
if not provider_settings.user_pool_id:
raise ValueError(
"user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID"
"user_pool_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID"
)
if not settings.client_id:
if not provider_settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID"
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID"
)
if not settings.client_secret:
if not provider_settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET"
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET"
)
# Apply defaults
required_scopes_final = settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
aws_region_final = settings.aws_region or "eu-central-1"
redirect_path_final = settings.redirect_path or "/auth/callback"
required_scopes_final = provider_settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
aws_region_final = provider_settings.aws_region or "eu-central-1"
redirect_path_final = provider_settings.redirect_path or "/auth/callback"
# Construct OIDC discovery URL
config_url = f"https://cognito-idp.{aws_region_final}.amazonaws.com/{settings.user_pool_id}/.well-known/openid-configuration"
config_url = f"https://cognito-idp.{aws_region_final}.amazonaws.com/{provider_settings.user_pool_id}/.well-known/openid-configuration"
# Extract secret string from SecretStr
client_secret_str = (
settings.client_secret.get_secret_value() if settings.client_secret else ""
provider_settings.client_secret.get_secret_value()
if provider_settings.client_secret
else ""
)
# Store Cognito-specific info for claim filtering
self.user_pool_id = settings.user_pool_id
self.user_pool_id = provider_settings.user_pool_id
self.aws_region = aws_region_final
# Initialize OIDC proxy with Cognito discovery
super().__init__(
config_url=config_url,
client_id=settings.client_id,
client_id=provider_settings.client_id,
client_secret=client_secret_str,
algorithm="RS256",
required_scopes=required_scopes_final,
base_url=settings.base_url,
issuer_url=settings.issuer_url,
base_url=provider_settings.base_url,
issuer_url=provider_settings.issuer_url,
redirect_path=redirect_path_final,
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage,
)
logger.info(
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s",
settings.client_id,
"Initialized AWS Cognito OAuth DCR provider for client %s with scopes: %s",
provider_settings.client_id,
required_scopes_final,
)
# Deprecated alias for backwards compatibility
class AWSCognitoProvider(AWSCognitoDCRProvider):
"""Deprecated: Use AWSCognitoDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"AWSCognitoProvider is deprecated, use AWSCognitoDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)
def get_token_verifier(
self,
*,

View file

@ -6,15 +6,21 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
from key_value.aio.protocols import AsyncKeyValue
from pydantic import SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
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.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -26,15 +32,32 @@ if TYPE_CHECKING:
logger = get_logger(__name__)
class AzureProviderSettings(BaseSettings):
"""Settings for Azure OAuth provider."""
class AzureDCRProviderSettings(BaseSettings):
"""Settings for Azure OAuth DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AZURE_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AZURE_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_AZURE_DCR_", "FASTMCP_SERVER_AUTH_AZURE_"],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
client_id: str | None = None
client_secret: SecretStr | None = None
tenant_id: str | None = None
@ -57,8 +80,8 @@ class AzureProviderSettings(BaseSettings):
return parse_scopes(v)
class AzureProvider(OAuthDCRProxy):
"""Azure (Microsoft Entra) OAuth provider for FastMCP.
class AzureDCRProvider(OAuthDCRProxy):
"""Azure (Microsoft Entra) OAuth DCR provider for FastMCP.
This provider implements Azure/Microsoft Entra ID authentication using the
OAuth Proxy pattern. It supports both organizational accounts and personal
@ -80,9 +103,9 @@ class AzureProvider(OAuthDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider
from fastmcp.server.auth.providers.azure import AzureDCRProvider
auth = AzureProvider(
auth = AzureDCRProvider(
client_id="your-client-id",
client_secret="your-client-secret",
tenant_id="your-tenant-id",
@ -132,7 +155,7 @@ class AzureProvider(OAuthDCRProxy):
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = AzureProviderSettings.model_validate(
provider_settings = AzureDCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -152,29 +175,33 @@ class AzureProvider(OAuthDCRProxy):
)
# Validate required settings
if not settings.client_id:
msg = "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID"
if not provider_settings.client_id:
msg = "client_id is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_ID"
raise ValueError(msg)
if not settings.client_secret:
msg = "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET"
if not provider_settings.client_secret:
msg = "client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_SECRET"
raise ValueError(msg)
# Validate tenant_id is provided
if not settings.tenant_id:
if not provider_settings.tenant_id:
msg = (
"tenant_id is required - set via parameter or "
"FASTMCP_SERVER_AUTH_AZURE_TENANT_ID. Use your Azure tenant ID "
"FASTMCP_SERVER_AUTH_AZURE_DCR_TENANT_ID. Use your Azure tenant ID "
"(found in Azure Portal), 'organizations', or 'consumers'"
)
raise ValueError(msg)
if not settings.required_scopes:
if not provider_settings.required_scopes:
raise ValueError("required_scopes is required")
# Apply defaults
self.identifier_uri = settings.identifier_uri or f"api://{settings.client_id}"
self.additional_authorize_scopes = settings.additional_authorize_scopes or []
tenant_id_final = settings.tenant_id
self.identifier_uri = (
provider_settings.identifier_uri or f"api://{provider_settings.client_id}"
)
self.additional_authorize_scopes = (
provider_settings.additional_authorize_scopes or []
)
tenant_id_final = provider_settings.tenant_id
# Always validate tokens against the app's API client ID using JWT
issuer = f"https://login.microsoftonline.com/{tenant_id_final}/v2.0"
@ -185,14 +212,16 @@ class AzureProvider(OAuthDCRProxy):
token_verifier = JWTVerifier(
jwks_uri=jwks_uri,
issuer=issuer,
audience=settings.client_id,
audience=provider_settings.client_id,
algorithm="RS256",
required_scopes=settings.required_scopes,
required_scopes=provider_settings.required_scopes,
)
# Extract secret string from SecretStr
client_secret_str = (
settings.client_secret.get_secret_value() if settings.client_secret else ""
provider_settings.client_secret.get_secret_value()
if provider_settings.client_secret
else ""
)
# Build Azure OAuth endpoints with tenant
@ -207,24 +236,41 @@ class AzureProvider(OAuthDCRProxy):
super().__init__(
upstream_authorization_endpoint=authorization_endpoint,
upstream_token_endpoint=token_endpoint,
upstream_client_id=settings.client_id,
upstream_client_id=provider_settings.client_id,
upstream_client_secret=client_secret_str,
token_verifier=token_verifier,
base_url=settings.base_url,
redirect_path=settings.redirect_path,
issuer_url=settings.issuer_url
or settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
base_url=provider_settings.base_url,
redirect_path=provider_settings.redirect_path,
issuer_url=provider_settings.issuer_url
or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=provider_settings.allowed_client_redirect_uris,
client_storage=client_storage,
)
logger.info(
"Initialized Azure OAuth provider for client %s with tenant %s%s",
settings.client_id,
"Initialized Azure OAuth DCR provider for client %s with tenant %s%s",
provider_settings.client_id,
tenant_id_final,
f" and identifier_uri {self.identifier_uri}" if self.identifier_uri else "",
)
# Deprecated alias for backwards compatibility
class AzureProvider(AzureDCRProvider):
"""Deprecated: Use AzureDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"AzureProvider is deprecated, use AzureDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)
async def authorize(
self,
client: OAuthClientInformationFull,

View file

@ -7,10 +7,10 @@ GitHub's OAuth flow, token validation, and user management.
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from fastmcp.server.auth.providers.github import GitHubDCRProvider
# Simple GitHub OAuth protection
auth = GitHubProvider(
auth = GitHubDCRProvider(
client_id="your-github-client-id",
client_secret="your-github-client-secret"
)
@ -21,15 +21,22 @@ Example:
from __future__ import annotations
import warnings
import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.settings import ENV_FILE
from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -37,15 +44,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
class GitHubProviderSettings(BaseSettings):
"""Settings for GitHub OAuth provider."""
class GitHubDCRProviderSettings(BaseSettings):
"""Settings for GitHub OAuth DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GITHUB_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GITHUB_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_GITHUB_DCR_", "FASTMCP_SERVER_AUTH_GITHUB_"],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
client_id: str | None = None
client_secret: SecretStr | None = None
base_url: AnyHttpUrl | str | None = None
@ -166,8 +190,8 @@ class GitHubTokenVerifier(TokenVerifier):
return None
class GitHubProvider(OAuthDCRProxy):
"""Complete GitHub OAuth provider for FastMCP.
class GitHubDCRProvider(OAuthDCRProxy):
"""Complete GitHub OAuth DCR provider for FastMCP.
This provider makes it trivial to add GitHub OAuth protection to any
FastMCP server. Just provide your GitHub OAuth app credentials and
@ -182,9 +206,9 @@ class GitHubProvider(OAuthDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider
from fastmcp.server.auth.providers.github import GitHubDCRProvider
auth = GitHubProvider(
auth = GitHubDCRProvider(
client_id="Ov23li...",
client_secret="abc123...",
base_url="https://my-server.com"
@ -223,7 +247,7 @@ class GitHubProvider(OAuthDCRProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = GitHubProviderSettings.model_validate(
provider_settings = GitHubDCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -241,20 +265,21 @@ class GitHubProvider(OAuthDCRProxy):
)
# Validate required settings
if not settings.client_id:
if not provider_settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID"
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID"
)
if not settings.client_secret:
if not provider_settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET"
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET"
)
# Apply defaults
timeout_seconds_final = settings.timeout_seconds or 10
required_scopes_final = settings.required_scopes or ["user"]
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
timeout_seconds_final = provider_settings.timeout_seconds or 10
required_scopes_final = provider_settings.required_scopes or ["user"]
allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
# Create GitHub token verifier
token_verifier = GitHubTokenVerifier(
@ -264,26 +289,45 @@ class GitHubProvider(OAuthDCRProxy):
# Extract secret string from SecretStr
client_secret_str = (
settings.client_secret.get_secret_value() if settings.client_secret else ""
provider_settings.client_secret.get_secret_value()
if provider_settings.client_secret
else ""
)
# Initialize OAuth proxy with GitHub endpoints
super().__init__(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id=settings.client_id,
upstream_client_id=provider_settings.client_id,
upstream_client_secret=client_secret_str,
token_verifier=token_verifier,
base_url=settings.base_url,
redirect_path=settings.redirect_path,
issuer_url=settings.issuer_url
or settings.base_url, # Default to base_url if not specified
base_url=provider_settings.base_url,
redirect_path=provider_settings.redirect_path,
issuer_url=provider_settings.issuer_url
or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage,
)
logger.info(
"Initialized GitHub OAuth provider for client %s with scopes: %s",
settings.client_id,
"Initialized GitHub OAuth DCR provider for client %s with scopes: %s",
provider_settings.client_id,
required_scopes_final,
)
# Deprecated alias for backwards compatibility
class GitHubProvider(GitHubDCRProvider):
"""Deprecated: Use GitHubDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"GitHubProvider is deprecated, use GitHubDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)

View file

@ -7,10 +7,10 @@ Google's OAuth flow, token validation, and user management.
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.google import GoogleProvider
from fastmcp.server.auth.providers.google import GoogleDCRProvider
# Simple Google OAuth protection
auth = GoogleProvider(
auth = GoogleDCRProvider(
client_id="your-google-client-id.apps.googleusercontent.com",
client_secret="your-google-client-secret"
)
@ -22,16 +22,22 @@ Example:
from __future__ import annotations
import time
import warnings
import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.settings import ENV_FILE
from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -39,15 +45,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
class GoogleProviderSettings(BaseSettings):
"""Settings for Google OAuth provider."""
class GoogleDCRProviderSettings(BaseSettings):
"""Settings for Google OAuth DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GOOGLE_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GOOGLE_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_GOOGLE_DCR_", "FASTMCP_SERVER_AUTH_GOOGLE_"],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
client_id: str | None = None
client_secret: SecretStr | None = None
base_url: AnyHttpUrl | str | None = None
@ -182,8 +205,8 @@ class GoogleTokenVerifier(TokenVerifier):
return None
class GoogleProvider(OAuthDCRProxy):
"""Complete Google OAuth provider for FastMCP.
class GoogleDCRProvider(OAuthDCRProxy):
"""Complete Google OAuth DCR provider for FastMCP.
This provider makes it trivial to add Google OAuth protection to any
FastMCP server. Just provide your Google OAuth app credentials and
@ -198,9 +221,9 @@ class GoogleProvider(OAuthDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.google import GoogleProvider
from fastmcp.server.auth.providers.google import GoogleDCRProvider
auth = GoogleProvider(
auth = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-abc123...",
base_url="https://my-server.com"
@ -242,7 +265,7 @@ class GoogleProvider(OAuthDCRProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = GoogleProviderSettings.model_validate(
provider_settings = GoogleDCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -260,20 +283,22 @@ class GoogleProvider(OAuthDCRProxy):
)
# Validate required settings
if not settings.client_id:
if not provider_settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID"
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_ID"
)
if not settings.client_secret:
if not provider_settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET"
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_SECRET"
)
# Apply defaults
timeout_seconds_final = settings.timeout_seconds or 10
timeout_seconds_final = provider_settings.timeout_seconds or 10
# Google requires at least one scope - openid is the minimal OIDC scope
required_scopes_final = settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
required_scopes_final = provider_settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
# Create Google token verifier
token_verifier = GoogleTokenVerifier(
@ -283,26 +308,45 @@ class GoogleProvider(OAuthDCRProxy):
# Extract secret string from SecretStr
client_secret_str = (
settings.client_secret.get_secret_value() if settings.client_secret else ""
provider_settings.client_secret.get_secret_value()
if provider_settings.client_secret
else ""
)
# Initialize OAuth proxy with Google endpoints
super().__init__(
upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth",
upstream_token_endpoint="https://oauth2.googleapis.com/token",
upstream_client_id=settings.client_id,
upstream_client_id=provider_settings.client_id,
upstream_client_secret=client_secret_str,
token_verifier=token_verifier,
base_url=settings.base_url,
redirect_path=settings.redirect_path,
issuer_url=settings.issuer_url
or settings.base_url, # Default to base_url if not specified
base_url=provider_settings.base_url,
redirect_path=provider_settings.redirect_path,
issuer_url=provider_settings.issuer_url
or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage,
)
logger.info(
"Initialized Google OAuth provider for client %s with scopes: %s",
settings.client_id,
"Initialized Google OAuth DCR provider for client %s with scopes: %s",
provider_settings.client_id,
required_scopes_final,
)
# Deprecated alias for backwards compatibility
class GoogleProvider(GoogleDCRProvider):
"""Deprecated: Use GoogleDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"GoogleProvider is deprecated, use GoogleDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)

View file

@ -2,7 +2,7 @@
This module provides two WorkOS authentication strategies:
1. WorkOSProvider - OAuth proxy for WorkOS Connect applications (non-DCR)
1. WorkOSDCRProvider - OAuth DCR proxy for WorkOS Connect applications
2. AuthKitProvider - DCR-compliant provider for WorkOS AuthKit
Choose based on your WorkOS setup and authentication requirements.
@ -10,17 +10,24 @@ Choose based on your WorkOS setup and authentication requirements.
from __future__ import annotations
import warnings
import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings
from starlette.responses import JSONResponse
from starlette.routing import Route
from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
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.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
settings,
)
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -28,15 +35,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__)
class WorkOSProviderSettings(BaseSettings):
"""Settings for WorkOS OAuth provider."""
class WorkOSDCRProviderSettings(BaseSettings):
"""Settings for WorkOS OAuth DCR provider."""
model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_WORKOS_",
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_WORKOS_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_WORKOS_DCR_", "FASTMCP_SERVER_AUTH_WORKOS_"],
env_file=ENV_FILE,
extra="ignore",
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
ExtendedEnvSettingsSource(settings_cls),
dotenv_settings,
file_secret_settings,
)
client_id: str | None = None
client_secret: SecretStr | None = None
authkit_domain: str | None = None # e.g., "https://your-app.authkit.app"
@ -125,14 +149,14 @@ class WorkOSTokenVerifier(TokenVerifier):
return None
class WorkOSProvider(OAuthDCRProxy):
"""Complete WorkOS OAuth provider for FastMCP.
class WorkOSDCRProvider(OAuthDCRProxy):
"""Complete WorkOS OAuth DCR provider for FastMCP.
This provider implements WorkOS AuthKit OAuth using the OAuth Proxy pattern.
This provider implements WorkOS AuthKit OAuth using the OAuth DCR Proxy pattern.
It provides OAuth2 authentication for users through WorkOS Connect applications.
Features:
- Transparent OAuth proxy to WorkOS AuthKit
- Transparent OAuth DCR proxy to WorkOS AuthKit
- Automatic token validation via userinfo endpoint
- User information extraction from ID tokens
- Support for standard OAuth scopes (openid, profile, email)
@ -146,9 +170,9 @@ class WorkOSProvider(OAuthDCRProxy):
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import WorkOSProvider
from fastmcp.server.auth.providers.workos import WorkOSDCRProvider
auth = WorkOSProvider(
auth = WorkOSDCRProvider(
client_id="client_123",
client_secret="sk_test_456",
authkit_domain="https://your-app.authkit.app",
@ -190,7 +214,7 @@ class WorkOSProvider(OAuthDCRProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
"""
settings = WorkOSProviderSettings.model_validate(
provider_settings = WorkOSDCRProviderSettings.model_validate(
{
k: v
for k, v in {
@ -209,31 +233,35 @@ class WorkOSProvider(OAuthDCRProxy):
)
# Validate required settings
if not settings.client_id:
if not provider_settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID"
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_ID"
)
if not settings.client_secret:
if not provider_settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET"
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_SECRET"
)
if not settings.authkit_domain:
if not provider_settings.authkit_domain:
raise ValueError(
"authkit_domain is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN"
"authkit_domain is required - set via parameter or FASTMCP_SERVER_AUTH_WORKOS_DCR_AUTHKIT_DOMAIN"
)
# Apply defaults and ensure authkit_domain is a full URL
authkit_domain_str = settings.authkit_domain
authkit_domain_str = provider_settings.authkit_domain
if not authkit_domain_str.startswith(("http://", "https://")):
authkit_domain_str = f"https://{authkit_domain_str}"
authkit_domain_final = authkit_domain_str.rstrip("/")
timeout_seconds_final = settings.timeout_seconds or 10
scopes_final = settings.required_scopes or []
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
timeout_seconds_final = provider_settings.timeout_seconds or 10
scopes_final = provider_settings.required_scopes or []
allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
# Extract secret string from SecretStr
client_secret_str = (
settings.client_secret.get_secret_value() if settings.client_secret else ""
provider_settings.client_secret.get_secret_value()
if provider_settings.client_secret
else ""
)
# Create WorkOS token verifier
@ -247,26 +275,43 @@ class WorkOSProvider(OAuthDCRProxy):
super().__init__(
upstream_authorization_endpoint=f"{authkit_domain_final}/oauth2/authorize",
upstream_token_endpoint=f"{authkit_domain_final}/oauth2/token",
upstream_client_id=settings.client_id,
upstream_client_id=provider_settings.client_id,
upstream_client_secret=client_secret_str,
token_verifier=token_verifier,
base_url=settings.base_url,
redirect_path=settings.redirect_path,
issuer_url=settings.issuer_url
or settings.base_url, # Default to base_url if not specified
base_url=provider_settings.base_url,
redirect_path=provider_settings.redirect_path,
issuer_url=provider_settings.issuer_url
or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage,
)
logger.info(
"Initialized WorkOS OAuth provider for client %s with AuthKit domain %s",
settings.client_id,
"Initialized WorkOS OAuth DCR provider for client %s with AuthKit domain %s",
provider_settings.client_id,
authkit_domain_final,
)
# Deprecated alias for backwards compatibility
class WorkOSProvider(WorkOSDCRProvider):
"""Deprecated: Use WorkOSDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
"""
def __init__(self, **kwargs):
if settings.deprecation_warnings:
warnings.warn(
"WorkOSProvider is deprecated, use WorkOSDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)
class AuthKitProviderSettings(BaseSettings):
model_config = SettingsConfigDict(
model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_",
env_file=ENV_FILE,
extra="ignore",

View file

@ -0,0 +1,63 @@
"""Test that deprecated provider imports still work.
This test file verifies that the old provider class names (without DCR suffix)
can still be imported and are subclasses of the new DCR providers.
"""
class TestDeprecatedProviderImports:
"""Test that deprecated provider names can be imported and are subclasses of DCR providers."""
def test_github_provider_import(self):
"""Test that GitHubProvider can be imported and is a GitHubDCRProvider subclass."""
from fastmcp.server.auth.providers.github import (
GitHubDCRProvider,
GitHubProvider,
)
assert GitHubProvider is not None
assert issubclass(GitHubProvider, GitHubDCRProvider)
def test_google_provider_import(self):
"""Test that GoogleProvider can be imported and is a GoogleDCRProvider subclass."""
from fastmcp.server.auth.providers.google import (
GoogleDCRProvider,
GoogleProvider,
)
assert GoogleProvider is not None
assert issubclass(GoogleProvider, GoogleDCRProvider)
def test_azure_provider_import(self):
"""Test that AzureProvider can be imported and is an AzureDCRProvider subclass."""
from fastmcp.server.auth.providers.azure import AzureDCRProvider, AzureProvider
assert AzureProvider is not None
assert issubclass(AzureProvider, AzureDCRProvider)
def test_workos_provider_import(self):
"""Test that WorkOSProvider can be imported and is a WorkOSDCRProvider subclass."""
from fastmcp.server.auth.providers.workos import (
WorkOSDCRProvider,
WorkOSProvider,
)
assert WorkOSProvider is not None
assert issubclass(WorkOSProvider, WorkOSDCRProvider)
def test_auth0_provider_import(self):
"""Test that Auth0Provider can be imported and is an Auth0DCRProvider subclass."""
from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider, Auth0Provider
assert Auth0Provider is not None
assert issubclass(Auth0Provider, Auth0DCRProvider)
def test_aws_cognito_provider_import(self):
"""Test that AWSCognitoProvider can be imported and is an AWSCognitoDCRProvider subclass."""
from fastmcp.server.auth.providers.aws import (
AWSCognitoDCRProvider,
AWSCognitoProvider,
)
assert AWSCognitoProvider is not None
assert issubclass(AWSCognitoProvider, AWSCognitoDCRProvider)

View file

@ -6,7 +6,10 @@ from unittest.mock import patch
import pytest
from fastmcp.server.auth.oidc_dcr_proxy import OIDCConfiguration
from fastmcp.server.auth.providers.auth0 import Auth0Provider, Auth0ProviderSettings
from fastmcp.server.auth.providers.auth0 import (
Auth0DCRProvider,
Auth0DCRProviderSettings,
)
from fastmcp.server.auth.providers.jwt import JWTVerifier
TEST_CONFIG_URL = "https://example.com/.well-known/openid-configuration"
@ -32,7 +35,7 @@ def valid_oidc_configuration_dict():
}
class TestAuth0ProviderSettings:
class TestAuth0DCRProviderSettings:
"""Test settings for Auth0 OAuth provider."""
def test_settings_from_env_vars(self):
@ -40,18 +43,18 @@ class TestAuth0ProviderSettings:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
"FASTMCP_SERVER_AUTH_AUTH0_REDIRECT_PATH": TEST_REDIRECT_PATH,
"FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES": ",".join(
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL": TEST_BASE_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_REDIRECT_PATH": TEST_REDIRECT_PATH,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_REQUIRED_SCOPES": ",".join(
TEST_REQUIRED_SCOPES
),
},
):
settings = Auth0ProviderSettings()
settings = Auth0DCRProviderSettings()
assert str(settings.config_url) == TEST_CONFIG_URL
assert settings.client_id == TEST_CLIENT_ID
@ -69,11 +72,11 @@ class TestAuth0ProviderSettings:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET": TEST_CLIENT_SECRET,
},
):
settings = Auth0ProviderSettings.model_validate(
settings = Auth0DCRProviderSettings.model_validate(
{
"client_id": "explicit_client_id",
"client_secret": "explicit_secret",
@ -87,20 +90,20 @@ class TestAuth0ProviderSettings:
)
class TestAuth0Provider:
"""Test Auth0Provider initialization."""
class TestAuth0DCRProvider:
"""Test Auth0DCRProvider initialization."""
def test_init_with_explicit_params(self, valid_oidc_configuration_dict):
"""Test initialization with explicit parameters."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
"fastmcp.server.auth.oidc_dcr_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
provider = Auth0Provider(
provider = Auth0DCRProvider(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
@ -141,16 +144,16 @@ class TestAuth0Provider:
patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
"FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL": TEST_BASE_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_REQUIRED_SCOPES": scopes_env,
},
),
patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
"fastmcp.server.auth.oidc_dcr_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get,
):
oidc_config = OIDCConfiguration.model_validate(
@ -158,7 +161,7 @@ class TestAuth0Provider:
)
mock_get.return_value = oidc_config
provider = Auth0Provider()
provider = Auth0DCRProvider()
mock_get.assert_called_once()
@ -183,15 +186,15 @@ class TestAuth0Provider:
patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_BASE_URL": TEST_BASE_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL": TEST_CONFIG_URL,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID": TEST_CLIENT_ID,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET": TEST_CLIENT_SECRET,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE": TEST_AUDIENCE,
"FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL": TEST_BASE_URL,
},
),
patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
"fastmcp.server.auth.oidc_dcr_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get,
):
oidc_config = OIDCConfiguration.model_validate(
@ -199,7 +202,7 @@ class TestAuth0Provider:
)
mock_get.return_value = oidc_config
provider = Auth0Provider(
provider = Auth0DCRProvider(
client_id="explicit_client",
client_secret="explicit_secret",
)
@ -214,28 +217,28 @@ class TestAuth0Provider:
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="config_url is required"):
Auth0Provider()
Auth0DCRProvider()
def test_init_missing_client_id_raises_error(self):
"""Test that missing client_id raises ValueError."""
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_id is required"):
Auth0Provider(config_url=TEST_CONFIG_URL)
Auth0DCRProvider(config_url=TEST_CONFIG_URL)
def test_init_missing_client_secret_raises_error(self):
"""Test that missing client_secret raises ValueError."""
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_secret is required"):
Auth0Provider(config_url=TEST_CONFIG_URL, client_id=TEST_CLIENT_ID)
Auth0DCRProvider(config_url=TEST_CONFIG_URL, client_id=TEST_CLIENT_ID)
def test_init_missing_audience_raises_error(self):
"""Test that missing audience raises ValueError."""
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="audience is required"):
Auth0Provider(
Auth0DCRProvider(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
@ -246,7 +249,7 @@ class TestAuth0Provider:
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="base_url is required"):
Auth0Provider(
Auth0DCRProvider(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
@ -256,14 +259,14 @@ class TestAuth0Provider:
def test_init_defaults(self, valid_oidc_configuration_dict):
"""Test that default values are applied correctly."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
"fastmcp.server.auth.oidc_dcr_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
provider = Auth0Provider(
provider = Auth0DCRProvider(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,

View file

@ -7,8 +7,8 @@ from unittest.mock import patch
import pytest
from fastmcp.server.auth.providers.aws import (
AWSCognitoProvider,
AWSCognitoProviderSettings,
AWSCognitoDCRProvider,
AWSCognitoDCRProviderSettings,
)
@ -38,7 +38,7 @@ def mock_cognito_oidc_discovery():
yield
class TestAWSCognitoProviderSettings:
class TestAWSCognitoDCRProviderSettings:
"""Test settings for AWS Cognito OAuth provider."""
def test_settings_from_env_vars(self):
@ -46,15 +46,15 @@ class TestAWSCognitoProviderSettings:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "us-east-1_XXXXXXXXX",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION": "us-east-1",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL": "https://example.com",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_REDIRECT_PATH": "/custom/callback",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID": "us-east-1_XXXXXXXXX",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_AWS_REGION": "us-east-1",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_BASE_URL": "https://example.com",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_REDIRECT_PATH": "/custom/callback",
},
):
settings = AWSCognitoProviderSettings()
settings = AWSCognitoDCRProviderSettings()
assert settings.user_pool_id == "us-east-1_XXXXXXXXX"
assert settings.aws_region == "us-east-1"
@ -71,12 +71,12 @@ class TestAWSCognitoProviderSettings:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "env_pool_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID": "env_pool_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET": "env_secret",
},
):
settings = AWSCognitoProviderSettings.model_validate(
settings = AWSCognitoDCRProviderSettings.model_validate(
{
"user_pool_id": "explicit_pool_id",
"client_id": "explicit_client_id",
@ -92,13 +92,13 @@ class TestAWSCognitoProviderSettings:
)
class TestAWSCognitoProvider:
"""Test AWSCognitoProvider initialization."""
class TestAWSCognitoDCRProvider:
"""Test AWSCognitoDCRProvider initialization."""
def test_init_with_explicit_params(self):
"""Test initialization with explicit parameters."""
with mock_cognito_oidc_discovery():
provider = AWSCognitoProvider(
provider = AWSCognitoDCRProvider(
user_pool_id="us-east-1_XXXXXXXXX",
aws_region="us-east-1",
client_id="test_client",
@ -137,16 +137,16 @@ class TestAWSCognitoProvider:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "us-east-1_XXXXXXXXX",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION": "us-east-1",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL": "https://env-example.com",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID": "us-east-1_XXXXXXXXX",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_AWS_REGION": "us-east-1",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_BASE_URL": "https://env-example.com",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_REQUIRED_SCOPES": scopes_env,
},
):
with mock_cognito_oidc_discovery():
provider = AWSCognitoProvider()
provider = AWSCognitoDCRProvider()
assert provider._upstream_client_id == "env_client_id"
assert (
@ -160,13 +160,13 @@ class TestAWSCognitoProvider:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID": "env_pool_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID": "env_pool_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET": "env_secret",
},
):
with mock_cognito_oidc_discovery():
provider = AWSCognitoProvider(
provider = AWSCognitoDCRProvider(
user_pool_id="explicit_pool_id",
client_id="explicit_client",
client_secret="explicit_secret",
@ -185,7 +185,7 @@ class TestAWSCognitoProvider:
"""Test that missing user_pool_id raises ValueError."""
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="user_pool_id is required"):
AWSCognitoProvider(
AWSCognitoDCRProvider(
client_id="test_client",
client_secret="test_secret",
)
@ -194,7 +194,7 @@ class TestAWSCognitoProvider:
"""Test that missing client_id raises ValueError."""
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_id is required"):
AWSCognitoProvider(
AWSCognitoDCRProvider(
user_pool_id="us-east-1_XXXXXXXXX",
client_secret="test_secret",
)
@ -203,7 +203,7 @@ class TestAWSCognitoProvider:
"""Test that missing client_secret raises ValueError."""
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_secret is required"):
AWSCognitoProvider(
AWSCognitoDCRProvider(
user_pool_id="us-east-1_XXXXXXXXX",
client_id="test_client",
)
@ -211,7 +211,7 @@ class TestAWSCognitoProvider:
def test_init_defaults(self):
"""Test that default values are applied correctly."""
with mock_cognito_oidc_discovery():
provider = AWSCognitoProvider(
provider = AWSCognitoDCRProvider(
user_pool_id="us-east-1_XXXXXXXXX",
client_id="test_client",
client_secret="test_secret",
@ -227,7 +227,7 @@ class TestAWSCognitoProvider:
def test_oidc_discovery_integration(self):
"""Test that OIDC discovery endpoints are used correctly."""
with mock_cognito_oidc_discovery():
provider = AWSCognitoProvider(
provider = AWSCognitoDCRProvider(
user_pool_id="us-west-2_YYYYYYYY",
aws_region="us-west-2",
client_id="test_client",

View file

@ -9,16 +9,16 @@ from mcp.server.auth.provider import AuthorizationParams
from mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyUrl
from fastmcp.server.auth.providers.azure import AzureProvider
from fastmcp.server.auth.providers.azure import AzureDCRProvider
from fastmcp.server.auth.providers.jwt import JWTVerifier
class TestAzureProvider:
class TestAzureDCRProvider:
"""Test Azure OAuth provider functionality."""
def test_init_with_explicit_params(self):
"""Test AzureProvider initialization with explicit parameters."""
provider = AzureProvider(
"""Test AzureDCRProvider initialization with explicit parameters."""
provider = AzureDCRProvider(
client_id="12345678-1234-1234-1234-123456789012",
client_secret="azure_secret_123",
tenant_id="87654321-4321-4321-4321-210987654321",
@ -43,18 +43,18 @@ class TestAzureProvider:
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test AzureProvider initialization from environment variables."""
"""Test AzureDCRProvider initialization from environment variables."""
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID": "env-client-id",
"FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET": "env-secret",
"FASTMCP_SERVER_AUTH_AZURE_TENANT_ID": "env-tenant-id",
"FASTMCP_SERVER_AUTH_AZURE_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_ID": "env-client-id",
"FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_SECRET": "env-secret",
"FASTMCP_SERVER_AUTH_AZURE_DCR_TENANT_ID": "env-tenant-id",
"FASTMCP_SERVER_AUTH_AZURE_DCR_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_AZURE_DCR_REQUIRED_SCOPES": scopes_env,
},
):
provider = AzureProvider()
provider = AzureDCRProvider()
assert provider._upstream_client_id == "env-client-id"
assert provider._upstream_client_secret.get_secret_value() == "env-secret"
@ -72,7 +72,7 @@ class TestAzureProvider:
def test_init_missing_client_id_raises_error(self):
"""Test that missing client_id raises ValueError."""
with pytest.raises(ValueError, match="client_id is required"):
AzureProvider(
AzureDCRProvider(
client_secret="test_secret",
tenant_id="test-tenant",
)
@ -80,7 +80,7 @@ class TestAzureProvider:
def test_init_missing_client_secret_raises_error(self):
"""Test that missing client_secret raises ValueError."""
with pytest.raises(ValueError, match="client_secret is required"):
AzureProvider(
AzureDCRProvider(
client_id="test_client",
tenant_id="test-tenant",
)
@ -88,14 +88,14 @@ class TestAzureProvider:
def test_init_missing_tenant_id_raises_error(self):
"""Test that missing tenant_id raises ValueError."""
with pytest.raises(ValueError, match="tenant_id is required"):
AzureProvider(
AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
)
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
@ -109,7 +109,7 @@ class TestAzureProvider:
def test_oauth_endpoints_configured_correctly(self):
"""Test that OAuth endpoints are configured correctly."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="my-tenant-id",
@ -133,7 +133,7 @@ class TestAzureProvider:
def test_special_tenant_values(self):
"""Test that special tenant values are accepted."""
# Test with "organizations"
provider1 = AzureProvider(
provider1 = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="organizations",
@ -143,7 +143,7 @@ class TestAzureProvider:
assert "/organizations/" in parsed.path
# Test with "consumers"
provider2 = AzureProvider(
provider2 = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="consumers",
@ -155,7 +155,7 @@ class TestAzureProvider:
def test_azure_specific_scopes(self):
"""Test handling of Azure-specific scope formats."""
# Just test that the provider accepts Azure-specific scopes without error
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
@ -173,7 +173,7 @@ class TestAzureProvider:
def test_init_does_not_require_api_client_id_anymore(self):
"""API client ID is no longer required; audience is client_id."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="test-tenant",
@ -183,7 +183,7 @@ class TestAzureProvider:
def test_init_with_custom_audience_uses_jwt_verifier(self):
"""When audience is provided, JWTVerifier is configured with JWKS and issuer."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="my-tenant",
@ -204,7 +204,7 @@ class TestAzureProvider:
@pytest.mark.asyncio
async def test_authorize_filters_resource_and_prefixes_scopes_with_audience(self):
"""authorize() should drop resource and prefix non-openid scopes with audience."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="common",
@ -255,7 +255,7 @@ class TestAzureProvider:
@pytest.mark.asyncio
async def test_authorize_appends_unprefixed_additional_scopes(self):
"""authorize() should append additional_authorize_scopes without prefixing them."""
provider = AzureProvider(
provider = AzureDCRProvider(
client_id="test_client",
client_secret="test_secret",
tenant_id="common",

View file

@ -1,4 +1,4 @@
"""Unit tests for GitHub OAuth provider."""
"""Unit tests for GitHub OAuth DCR provider."""
import os
from unittest.mock import MagicMock, patch
@ -6,28 +6,28 @@ from unittest.mock import MagicMock, patch
import pytest
from fastmcp.server.auth.providers.github import (
GitHubProvider,
GitHubProviderSettings,
GitHubDCRProvider,
GitHubDCRProviderSettings,
GitHubTokenVerifier,
)
class TestGitHubProviderSettings:
"""Test settings for GitHub OAuth provider."""
class TestGitHubDCRProviderSettings:
"""Test settings for GitHub OAuth DCR provider."""
def test_settings_from_env_vars(self):
"""Test that settings can be loaded from environment variables."""
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://example.com",
"FASTMCP_SERVER_AUTH_GITHUB_REDIRECT_PATH": "/custom/callback",
"FASTMCP_SERVER_AUTH_GITHUB_TIMEOUT_SECONDS": "30",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_BASE_URL": "https://example.com",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_REDIRECT_PATH": "/custom/callback",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_TIMEOUT_SECONDS": "30",
},
):
settings = GitHubProviderSettings()
settings = GitHubDCRProviderSettings()
assert settings.client_id == "env_client_id"
assert (
@ -43,11 +43,11 @@ class TestGitHubProviderSettings:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET": "env_secret",
},
):
settings = GitHubProviderSettings.model_validate(
settings = GitHubDCRProviderSettings.model_validate(
{
"client_id": "explicit_client_id",
"client_secret": "explicit_secret",
@ -61,12 +61,12 @@ class TestGitHubProviderSettings:
)
class TestGitHubProvider:
"""Test GitHubProvider initialization."""
class TestGitHubDCRProvider:
"""Test GitHubDCRProvider initialization."""
def test_init_with_explicit_params(self):
"""Test initialization with explicit parameters."""
provider = GitHubProvider(
provider = GitHubDCRProvider(
client_id="test_client",
client_secret="test_secret",
base_url="https://example.com",
@ -95,13 +95,13 @@ class TestGitHubProvider:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://env-example.com",
"FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_BASE_URL": "https://env-example.com",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_REQUIRED_SCOPES": scopes_env,
},
):
provider = GitHubProvider()
provider = GitHubDCRProvider()
assert provider._upstream_client_id == "env_client_id"
assert provider._upstream_client_secret.get_secret_value() == "env_secret"
@ -113,11 +113,11 @@ class TestGitHubProvider:
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET": "env_secret",
},
):
provider = GitHubProvider(
provider = GitHubDCRProvider(
client_id="explicit_client",
client_secret="explicit_secret",
)
@ -132,18 +132,18 @@ class TestGitHubProvider:
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_id is required"):
GitHubProvider(client_secret="test_secret")
GitHubDCRProvider(client_secret="test_secret")
def test_init_missing_client_secret_raises_error(self):
"""Test that missing client_secret raises ValueError."""
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_secret is required"):
GitHubProvider(client_id="test_client")
GitHubDCRProvider(client_id="test_client")
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = GitHubProvider(
provider = GitHubDCRProvider(
client_id="test_client",
client_secret="test_secret",
)

View file

@ -5,15 +5,15 @@ from unittest.mock import patch
import pytest
from fastmcp.server.auth.providers.google import GoogleProvider
from fastmcp.server.auth.providers.google import GoogleDCRProvider
class TestGoogleProvider:
class TestGoogleDCRProvider:
"""Test Google OAuth provider functionality."""
def test_init_with_explicit_params(self):
"""Test GoogleProvider initialization with explicit parameters."""
provider = GoogleProvider(
"""Test GoogleDCRProvider initialization with explicit parameters."""
provider = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-test123",
base_url="https://myserver.com",
@ -32,17 +32,17 @@ class TestGoogleProvider:
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test GoogleProvider initialization from environment variables."""
"""Test GoogleDCRProvider initialization from environment variables."""
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID": "env123.apps.googleusercontent.com",
"FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET": "GOCSPX-env456",
"FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_ID": "env123.apps.googleusercontent.com",
"FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_SECRET": "GOCSPX-env456",
"FASTMCP_SERVER_AUTH_GOOGLE_DCR_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_GOOGLE_DCR_REQUIRED_SCOPES": scopes_env,
},
):
provider = GoogleProvider()
provider = GoogleDCRProvider()
assert provider._upstream_client_id == "env123.apps.googleusercontent.com"
assert (
@ -59,18 +59,18 @@ class TestGoogleProvider:
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_id is required"):
GoogleProvider(client_secret="GOCSPX-test123")
GoogleDCRProvider(client_secret="GOCSPX-test123")
def test_init_missing_client_secret_raises_error(self):
"""Test that missing client_secret raises ValueError."""
# Clear environment variables to test proper error handling
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(ValueError, match="client_secret is required"):
GoogleProvider(client_id="123456789.apps.googleusercontent.com")
GoogleDCRProvider(client_id="123456789.apps.googleusercontent.com")
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = GoogleProvider(
provider = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-test123",
)
@ -82,7 +82,7 @@ class TestGoogleProvider:
def test_oauth_endpoints_configured_correctly(self):
"""Test that OAuth endpoints are configured correctly."""
provider = GoogleProvider(
provider = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-test123",
base_url="https://myserver.com",
@ -102,7 +102,7 @@ class TestGoogleProvider:
def test_google_specific_scopes(self):
"""Test handling of Google-specific scope formats."""
# Just test that the provider accepts Google-specific scopes without error
provider = GoogleProvider(
provider = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-test123",
required_scopes=[

View file

@ -9,16 +9,16 @@ import pytest
from fastmcp import Client, FastMCP
from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.server.auth.providers.workos import AuthKitProvider, WorkOSProvider
from fastmcp.server.auth.providers.workos import AuthKitProvider, WorkOSDCRProvider
from fastmcp.utilities.tests import HeadlessOAuth, run_server_async
class TestWorkOSProvider:
class TestWorkOSDCRProvider:
"""Test WorkOS OAuth provider functionality."""
def test_init_with_explicit_params(self):
"""Test WorkOSProvider initialization with explicit parameters."""
provider = WorkOSProvider(
"""Test WorkOSDCRProvider initialization with explicit parameters."""
provider = WorkOSDCRProvider(
client_id="client_test123",
client_secret="secret_test456",
authkit_domain="https://test.authkit.app",
@ -38,18 +38,18 @@ class TestWorkOSProvider:
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test WorkOSProvider initialization from environment variables."""
"""Test WorkOSDCRProvider initialization from environment variables."""
with patch.dict(
os.environ,
{
"FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID": "env_client",
"FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN": "https://env.authkit.app",
"FASTMCP_SERVER_AUTH_WORKOS_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES": scopes_env,
"FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_ID": "env_client",
"FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_WORKOS_DCR_AUTHKIT_DOMAIN": "https://env.authkit.app",
"FASTMCP_SERVER_AUTH_WORKOS_DCR_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_WORKOS_DCR_REQUIRED_SCOPES": scopes_env,
},
):
provider = WorkOSProvider()
provider = WorkOSDCRProvider()
assert provider._upstream_client_id == "env_client"
assert provider._upstream_client_secret.get_secret_value() == "env_secret"
@ -62,7 +62,7 @@ class TestWorkOSProvider:
def test_init_missing_client_id_raises_error(self):
"""Test that missing client_id raises ValueError."""
with pytest.raises(ValueError, match="client_id is required"):
WorkOSProvider(
WorkOSDCRProvider(
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
)
@ -70,7 +70,7 @@ class TestWorkOSProvider:
def test_init_missing_client_secret_raises_error(self):
"""Test that missing client_secret raises ValueError."""
with pytest.raises(ValueError, match="client_secret is required"):
WorkOSProvider(
WorkOSDCRProvider(
client_id="test_client",
authkit_domain="https://test.authkit.app",
)
@ -78,7 +78,7 @@ class TestWorkOSProvider:
def test_init_missing_authkit_domain_raises_error(self):
"""Test that missing authkit_domain raises ValueError."""
with pytest.raises(ValueError, match="authkit_domain is required"):
WorkOSProvider(
WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
)
@ -86,7 +86,7 @@ class TestWorkOSProvider:
def test_authkit_domain_https_prefix_handling(self):
"""Test that authkit_domain handles missing https:// prefix."""
# Without https:// - should add it
provider1 = WorkOSProvider(
provider1 = WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="test.authkit.app",
@ -98,7 +98,7 @@ class TestWorkOSProvider:
assert parsed.path == "/oauth2/authorize"
# With https:// - should keep it
provider2 = WorkOSProvider(
provider2 = WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
@ -110,7 +110,7 @@ class TestWorkOSProvider:
assert parsed.path == "/oauth2/authorize"
# With http:// - should be preserved
provider3 = WorkOSProvider(
provider3 = WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="http://localhost:8080",
@ -123,7 +123,7 @@ class TestWorkOSProvider:
def test_init_defaults(self):
"""Test that default values are applied correctly."""
provider = WorkOSProvider(
provider = WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",
@ -136,7 +136,7 @@ class TestWorkOSProvider:
def test_oauth_endpoints_configured_correctly(self):
"""Test that OAuth endpoints are configured correctly."""
provider = WorkOSProvider(
provider = WorkOSDCRProvider(
client_id="test_client",
client_secret="test_secret",
authkit_domain="https://test.authkit.app",