Compare commits

...

16 commits

Author SHA1 Message Date
Jeremiah Lowin
bf3c674d49 Merge branch 'main' into dcr-proxy 2025-10-20 19:27:05 -04:00
Jeremiah Lowin
e55cc531ad Apply PR #2156 logging changes to oauth_dcr_proxy.py
- Remove info/warning logs for allowed_client_redirect_uris
- Add 'and use persistent storage' to production guidance for JWT signing key and token encryption key
2025-10-20 19:25:07 -04:00
Jeremiah Lowin
68c061d565 Remove breakpoint from settings.py 2025-10-20 19:20:01 -04:00
Jeremiah Lowin
7118cc5ad9 Fix deprecated access 2025-10-20 19:17:07 -04:00
Jeremiah Lowin
598b10090e Use full path for settings.deprecation_warnings access
Changed from settings_module.settings.deprecation_warnings to
fastmcp.settings.settings.deprecation_warnings for clarity.
2025-10-20 19:14:44 -04:00
Jeremiah Lowin
17b60c8815 Fix settings import pattern in OAuth providers
Use direct imports for non-deprecated items (ENV_FILE, ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict) and settings_module for the deprecated settings instance.
2025-10-20 19:12:56 -04:00
Jeremiah Lowin
a9b77e76d0 Remove outdated oidc_proxy test file
This test file was testing the old oidc_proxy module which has been renamed
to oidc_dcr_proxy. The module is only used internally by providers and doesn't
need backwards compatibility.
2025-10-20 18:07:28 -04:00
Jeremiah Lowin
bd2c554e44 Move authorize method from deprecated AzureProvider to AzureDCRProvider
The authorize method with scope prefixing and resource filtering belongs on
the main AzureDCRProvider class, not just the deprecated alias.
2025-10-20 18:07:12 -04:00
Jeremiah Lowin
fc9f7197ec Update SDK 2025-10-20 18:04:53 -04:00
Jeremiah Lowin
9e0c2a2900 Fix ty type checker errors by using ExtendedSettingsConfigDict
Use ExtendedSettingsConfigDict instead of SettingsConfigDict in all provider
Settings classes to support env_prefixes field. This matches the pattern used
in src/fastmcp/settings.py and resolves type checking errors.
2025-10-20 18:01:36 -04:00
Jeremiah Lowin
5b66270a09 Fix settings import pattern in OAuth providers to avoid deprecation warnings
Changed all 6 provider files from 'from fastmcp.settings import settings'
to 'import fastmcp.settings as settings_module' to avoid triggering the
settings import deprecation warning.

Also simplified deprecation tests to only verify imports and subclass
relationships without instantiating providers.
2025-10-20 17:56:04 -04:00
Jeremiah Lowin
f5c5d20517 Update documentation to use DCR-suffixed provider names
All OAuth provider references updated from old names (GitHubProvider,
GoogleProvider, etc.) to new DCR-suffixed names (GitHubDCRProvider,
GoogleDCRProvider, etc.) including environment variable names.

Updated files:
- Authentication docs (oauth-proxy.mdx, oidc-proxy.mdx, authentication.mdx)
- Integration guides (github.mdx, google.mdx, azure.mdx, workos.mdx, auth0.mdx, aws-cognito.mdx)
- Deployment guide (http.mdx)
- Storage backends guide (storage-backends.mdx)
- Upgrade guide (upgrade-guide.mdx)
2025-10-20 17:46:42 -04:00
Jeremiah Lowin
a48d753d16 Rename OAuth providers to include DCR suffix
Renamed all OAuth provider classes to include "DCR" suffix to clarify they use
Dynamic Client Registration, distinguishing them from future SEP 991 implementations.

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

Changes:
- Renamed all provider classes and settings classes with DCR suffix
- Updated environment variable prefixes to include _DCR_
- Added backwards compatibility via env_prefixes array (old vars still work)
- Created deprecated alias classes that emit deprecation warnings
- Updated all provider tests to use new DCR naming
- Fixed Auth0 tests to use oidc_dcr_proxy instead of deprecated oidc_proxy
- Created deprecation test suite to verify old names can be imported
2025-10-20 17:37:16 -04:00
Jeremiah Lowin
0ad638003c 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.
2025-10-20 17:32:35 -04:00
Jeremiah Lowin
deb0c3ea95 Rename OIDCProxy -> OIDCDCRProxy 2025-10-20 16:01:36 -04:00
Jeremiah Lowin
dd86edf275 Rename OAuthProxy -> OAuthDCRProxy 2025-10-20 15:41:16 -04:00
54 changed files with 4011 additions and 4113 deletions

View file

@ -394,7 +394,7 @@ When mounting an OAuth-protected server under a path prefix, declare your URLs u
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.routing import Mount from starlette.routing import Mount
@ -407,7 +407,7 @@ MCP_PATH = "/mcp"
Create the auth provider with both `issuer_url` and `base_url`: Create the auth provider with both `issuer_url` and `base_url`:
```python ```python
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-client-id", client_id="your-client-id",
client_secret="your-client-secret", client_secret="your-client-secret",
issuer_url=ROOT_URL, # Discovery metadata at root issuer_url=ROOT_URL, # Discovery metadata at root
@ -454,7 +454,7 @@ Here's a complete working example showing all the pieces together:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.routing import Mount from starlette.routing import Mount
import uvicorn import uvicorn
@ -465,7 +465,7 @@ MOUNT_PREFIX = "/api"
MCP_PATH = "/mcp" MCP_PATH = "/mcp"
# Create OAuth provider # Create OAuth provider
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-client-id", client_id="your-client-id",
client_secret="your-client-secret", client_secret="your-client-secret",
issuer_url=ROOT_URL, issuer_url=ROOT_URL,
@ -565,7 +565,7 @@ The two keys can be any secret strings (environment variables, secret manager, e
Add two parameters to your auth provider and use persistent storage and HTTPS: Add two parameters to your auth provider and use persistent storage and HTTPS:
```python {4-7} ```python {4-7}
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id=os.environ["GITHUB_CLIENT_ID"], client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"], client_secret=os.environ["GITHUB_CLIENT_SECRET"],
jwt_signing_key=os.environ["JWT_SIGNING_KEY"], jwt_signing_key=os.environ["JWT_SIGNING_KEY"],

View file

@ -25,7 +25,7 @@ By default, these keys are ephemeral (random salt at startup, not persisted). Fo
If you want tokens to survive server restarts, add two new parameters: If you want tokens to survive server restarts, add two new parameters:
```python ```python
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id=os.environ["GITHUB_CLIENT_ID"], client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"], client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url="https://your-server.com", base_url="https://your-server.com",

View file

@ -19,12 +19,11 @@
"light": "#4cc9f0", "light": "#4cc9f0",
"primary": "#2d00f7" "primary": "#2d00f7"
}, },
"fonts": {
"heading": { "family": "Google Sans" },
"body": { "family": "BlinkMacSystemFont" }
},
"contextual": { "contextual": {
"options": ["copy", "view"] "options": [
"copy",
"view"
]
}, },
"description": "The fast, Pythonic way to build MCP servers and clients.", "description": "The fast, Pythonic way to build MCP servers and clients.",
"errors": { "errors": {
@ -38,6 +37,14 @@
"dark": "/assets/brand/favicon.svg", "dark": "/assets/brand/favicon.svg",
"light": "/assets/brand/favicon.svg" "light": "/assets/brand/favicon.svg"
}, },
"fonts": {
"body": {
"family": "BlinkMacSystemFont"
},
"heading": {
"family": "Google Sans"
}
},
"footer": { "footer": {
"socials": { "socials": {
"discord": "https://discord.gg/uu8dJCgttd", "discord": "https://discord.gg/uu8dJCgttd",
@ -150,7 +157,10 @@
{ {
"group": "Essentials", "group": "Essentials",
"icon": "cube", "icon": "cube",
"pages": ["clients/client", "clients/transports"] "pages": [
"clients/client",
"clients/transports"
]
}, },
{ {
"group": "Core Operations", "group": "Core Operations",
@ -176,7 +186,10 @@
{ {
"group": "Authentication", "group": "Authentication",
"icon": "user-shield", "icon": "user-shield",
"pages": ["clients/auth/oauth", "clients/auth/bearer"] "pages": [
"clients/auth/oauth",
"clients/auth/bearer"
]
} }
] ]
}, },
@ -230,7 +243,10 @@
{ {
"group": "API Integration", "group": "API Integration",
"icon": "globe", "icon": "globe",
"pages": ["integrations/fastapi", "integrations/openapi"] "pages": [
"integrations/fastapi",
"integrations/openapi"
]
} }
] ]
}, },
@ -336,7 +352,9 @@
"python-sdk/fastmcp-server-auth-__init__", "python-sdk/fastmcp-server-auth-__init__",
"python-sdk/fastmcp-server-auth-auth", "python-sdk/fastmcp-server-auth-auth",
"python-sdk/fastmcp-server-auth-jwt_issuer", "python-sdk/fastmcp-server-auth-jwt_issuer",
"python-sdk/fastmcp-server-auth-oauth_dcr_proxy",
"python-sdk/fastmcp-server-auth-oauth_proxy", "python-sdk/fastmcp-server-auth-oauth_proxy",
"python-sdk/fastmcp-server-auth-oidc_dcr_proxy",
"python-sdk/fastmcp-server-auth-oidc_proxy", "python-sdk/fastmcp-server-auth-oidc_proxy",
{ {
"group": "providers", "group": "providers",
@ -461,17 +479,17 @@
"search": { "search": {
"prompt": "Search the docs..." "prompt": "Search the docs..."
}, },
"styling": {
"codeblocks": {
"theme": {
"dark": "dark-plus",
"light": "snazzy-light"
}
}
},
"theme": "almond", "theme": "almond",
"thumbnails": { "thumbnails": {
"appearance": "light", "appearance": "light",
"background": "/assets/brand/thumbnail-background.png" "background": "/assets/brand/thumbnail-background.png"
},
"styling": {
"codeblocks": {
"theme": {
"light": "snazzy-light",
"dark": "dark-plus"
}
}
} }
} }

View file

@ -48,7 +48,7 @@ Create an Application in your Auth0 settings to get the credentials needed for a
</Warning> </Warning>
<Tip> <Tip>
If you want to use a custom callback path (e.g., `/auth/auth0/callback`), make sure to set the same path in both your Auth0 Application settings and the `redirect_path` parameter when configuring the Auth0Provider. If you want to use a custom callback path (e.g., `/auth/auth0/callback`), make sure to set the same path in both your Auth0 Application settings and the `redirect_path` parameter when configuring the Auth0DCRProvider.
</Tip> </Tip>
</Step> </Step>
@ -77,14 +77,14 @@ Create an Application in your Auth0 settings to get the credentials needed for a
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `Auth0Provider`. Create your FastMCP server using the `Auth0DCRProvider`.
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# The Auth0Provider utilizes Auth0 OIDC configuration # The Auth0DCRProvider utilizes Auth0 OIDC configuration
auth_provider = Auth0Provider( auth_provider = Auth0DCRProvider(
config_url="https://.../.well-known/openid-configuration", # Your Auth0 configuration URL config_url="https://.../.well-known/openid-configuration", # Your Auth0 configuration URL
client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", # Your Auth0 application Client ID client_id="tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB", # Your Auth0 application Client ID
client_secret="vPYqbjemq...", # Your Auth0 application Client Secret client_secret="vPYqbjemq...", # Your Auth0 application Client Secret
@ -163,7 +163,7 @@ Setting this environment variable allows the Auth0 provider to be used automatic
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.auth0.Auth0Provider` to use Auth0 authentication. Set to `fastmcp.server.auth.providers.auth0.Auth0DCRProvider` to use Auth0 authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -172,51 +172,51 @@ Set to `fastmcp.server.auth.providers.auth0.Auth0Provider` to use Auth0 authenti
These environment variables provide default values for the Auth0 provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the Auth0 provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL" required> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL" required>
Your Auth0 Application Configuration URL (e.g., `https://.../.well-known/openid-configuration`) Your Auth0 Application Configuration URL (e.g., `https://.../.well-known/openid-configuration`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID" required>
Your Auth0 Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`) Your Auth0 Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET" required>
Your Auth0 Application Client Secret (e.g., `vPYqbjemq...`) Your Auth0 Application Client Secret (e.g., `vPYqbjemq...`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE" required> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE" required>
Your Auth0 API Audience Your Auth0 API Audience
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_BASE_URL" required> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL" required>
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_REDIRECT_PATH" default="/auth/callback">
Redirect path configured in your Auth0 Application Redirect path configured in your Auth0 Application
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES" default='["openid"]'> <ParamField path="FASTMCP_SERVER_AUTH_AUTH0_DCR_REQUIRED_SCOPES" default='["openid"]'>
Comma-, space-, or JSON-separated list of required AUth0 scopes (e.g., `openid email` or `["openid","email"]`) Comma-, space-, or JSON-separated list of required Auth0 scopes (e.g., `openid email` or `["openid","email"]`)
</ParamField> </ParamField>
</Card> </Card>
Example `.env` file: Example `.env` file:
```bash ```bash
# Use the Auth0 provider # Use the Auth0 provider
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0DCRProvider
# Auth0 configuration and credentials # Auth0 configuration and credentials
FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL=https://.../.well-known/openid-configuration
FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB
FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq... FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET=vPYqbjemq...
FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://... FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE=https://...
FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_AUTH0_REQUIRED_SCOPES=openid,email FASTMCP_SERVER_AUTH_AUTH0_DCR_REQUIRED_SCOPES=openid,email
``` ```
With environment variables set, your server code simplifies to: With environment variables set, your server code simplifies to:

View file

@ -117,15 +117,15 @@ Set up AWS Cognito user pool with an app client to get the credentials needed fo
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `AWSCognitoProvider`, which handles AWS Cognito's JWT tokens and user claims automatically: Create your FastMCP server using the `AWSCognitoDCRProvider`, which handles AWS Cognito's JWT tokens and user claims automatically:
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.aws import AWSCognitoProvider from fastmcp.server.auth.providers.aws import AWSCognitoDCRProvider
from fastmcp.server.dependencies import get_access_token from fastmcp.server.dependencies import get_access_token
# The AWSCognitoProvider handles JWT validation and user claims # The AWSCognitoDCRProvider handles JWT validation and user claims
auth_provider = AWSCognitoProvider( auth_provider = AWSCognitoDCRProvider(
user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID user_pool_id="eu-central-1_XXXXXXXXX", # Your AWS Cognito user pool ID
aws_region="eu-central-1", # AWS region (defaults to eu-central-1) aws_region="eu-central-1", # AWS region (defaults to eu-central-1)
client_id="your-app-client-id", # Your app client ID client_id="your-app-client-id", # Your app client ID
@ -206,7 +206,7 @@ Setting this environment variable allows the AWS Cognito provider to be used aut
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.aws.AWSCognitoProvider` to use AWS Cognito authentication. Set to `fastmcp.server.auth.providers.aws.AWSCognitoDCRProvider` to use AWS Cognito authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -215,35 +215,35 @@ Set to `fastmcp.server.auth.providers.aws.AWSCognitoProvider` to use AWS Cognito
These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the AWS Cognito provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID" required>
Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`) Your AWS Cognito user pool ID (e.g., `eu-central-1_XXXXXXXXX`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION" default="eu-central-1"> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_AWS_REGION" default="eu-central-1">
AWS region where your AWS Cognito user pool is located AWS region where your AWS Cognito user pool is located
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID" required>
Your AWS Cognito app client ID Your AWS Cognito app client ID
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET" required>
Your AWS Cognito app client secret Your AWS Cognito app client secret
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL" default="http://localhost:8000"> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_BASE_URL" default="http://localhost:8000">
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_REDIRECT_PATH" default="/auth/callback">
One of the redirect paths configured in your AWS Cognito app client One of the redirect paths configured in your AWS Cognito app client
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES" default='["openid"]'> <ParamField path="FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_REQUIRED_SCOPES" default='["openid"]'>
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`) Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid email` or `["openid","email","profile"]`)
</ParamField> </ParamField>
</Card> </Card>
@ -251,15 +251,15 @@ Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid e
Example `.env` file: Example `.env` file:
```bash ```bash
# Use the AWS Cognito provider # Use the AWS Cognito provider
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoProvider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.aws.AWSCognitoDCRProvider
# AWS Cognito credentials # AWS Cognito credentials
FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID=eu-central-1_XXXXXXXXX FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_USER_POOL_ID=eu-central-1_XXXXXXXXX
FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION=eu-central-1 FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_AWS_REGION=eu-central-1
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID=your-app-client-id FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_ID=your-app-client-id
FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET=your-app-client-secret FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_CLIENT_SECRET=your-app-client-secret
FASTMCP_SERVER_AUTH_AWS_COGNITO_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_AWS_COGNITO_REQUIRED_SCOPES=openid,email,profile FASTMCP_SERVER_AUTH_AWS_COGNITO_DCR_REQUIRED_SCOPES=openid,email,profile
``` ```
With environment variables set, your server code simplifies to: With environment variables set, your server code simplifies to:

View file

@ -47,7 +47,7 @@ Create an App registration in Azure Portal to get the credentials needed for aut
</Warning> </Warning>
<Tip> <Tip>
If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureProvider. If you want to use a custom callback path (e.g., `/auth/azure/callback`), make sure to set the same path in both your Azure App registration and the `redirect_path` parameter when configuring the AzureDCRProvider.
</Tip> </Tip>
- **Expose an API**: Configure your Application ID URI and define scopes - **Expose an API**: Configure your Application ID URI and define scopes
@ -75,7 +75,7 @@ Create an App registration in Azure Portal to get the credentials needed for aut
</Warning> </Warning>
<Note> <Note>
In FastMCP's `AzureProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`. In FastMCP's `AzureDCRProvider`, set `identifier_uri` to your Application ID URI (optional; defaults to `api://{client_id}`) and set `required_scopes` to the unprefixed scope names (e.g., `read`, `write`). During authorization, FastMCP automatically prefixes scopes with your `identifier_uri`.
</Note> </Note>
@ -110,14 +110,14 @@ Create an App registration in Azure Portal to get the credentials needed for aut
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `AzureProvider`, which handles Azure's OAuth flow automatically: Create your FastMCP server using the `AzureDCRProvider`, which handles Azure's OAuth flow automatically:
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider from fastmcp.server.auth.providers.azure import AzureDCRProvider
# The AzureProvider handles Azure's token format and validation # The AzureDCRProvider handles Azure's token format and validation
auth_provider = AzureProvider( auth_provider = AzureDCRProvider(
client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", # Your Azure App Client ID client_id="835f09b6-0f0f-40cc-85cb-f32c5829a149", # Your Azure App Client ID
client_secret="your-client-secret", # Your Azure App Client Secret client_secret="your-client-secret", # Your Azure App Client Secret
tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED) tenant_id="08541b6e-646d-43de-a0eb-834e6713d6d5", # Your Azure Tenant ID (REQUIRED)
@ -139,7 +139,7 @@ async def get_user_info() -> dict:
from fastmcp.server.dependencies import get_access_token from fastmcp.server.dependencies import get_access_token
token = get_access_token() token = get_access_token()
# The AzureProvider stores user data in token claims # The AzureDCRProvider stores user data in token claims
return { return {
"azure_id": token.claims.get("sub"), "azure_id": token.claims.get("sub"),
"email": token.claims.get("email"), "email": token.claims.get("email"),
@ -217,7 +217,7 @@ Setting this environment variable allows the Azure provider to be used automatic
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.azure.AzureProvider` to use Azure authentication. Set to `fastmcp.server.auth.providers.azure.AzureDCRProvider` to use Azure authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -226,15 +226,15 @@ Set to `fastmcp.server.auth.providers.azure.AzureProvider` to use Azure authenti
These environment variables provide default values for the Azure provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the Azure provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_ID" required>
Your Azure App registration Client ID (e.g., `835f09b6-0f0f-40cc-85cb-f32c5829a149`) Your Azure App registration Client ID (e.g., `835f09b6-0f0f-40cc-85cb-f32c5829a149`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_SECRET" required>
Your Azure App registration Client Secret Your Azure App registration Client Secret
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_TENANT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_TENANT_ID" required>
Your Azure tenant ID (specific ID, "organizations", or "consumers") Your Azure tenant ID (specific ID, "organizations", or "consumers")
<Note> <Note>
@ -242,27 +242,27 @@ This is **REQUIRED**. Find your tenant ID in Azure Portal under Microsoft Entra
</Note> </Note>
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_BASE_URL" default="http://localhost:8000"> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_BASE_URL" default="http://localhost:8000">
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_REDIRECT_PATH" default="/auth/callback">
Redirect path configured in your Azure App registration Redirect path configured in your Azure App registration
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES" default=""> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_REQUIRED_SCOPES" default="">
Comma-, space-, or JSON-separated list of required scopes for your API. These are validated on tokens and used as defaults if the client does not request specific scopes. Comma-, space-, or JSON-separated list of required scopes for your API. These are validated on tokens and used as defaults if the client does not request specific scopes.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES" default=""> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_ADDITIONAL_AUTHORIZE_SCOPES" default="">
Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation. Comma-, space-, or JSON-separated list of additional scopes to include in the authorization request without prefixing. Use this to request upstream scopes such as Microsoft Graph permissions. These are not used for token validation.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI" default="api://{client_id}"> <ParamField path="FASTMCP_SERVER_AUTH_AZURE_DCR_IDENTIFIER_URI" default="api://{client_id}">
Application ID URI used to prefix scopes during authorization. Application ID URI used to prefix scopes during authorization.
</ParamField> </ParamField>
</Card> </Card>
@ -270,18 +270,18 @@ Application ID URI used to prefix scopes during authorization.
Example `.env` file: Example `.env` file:
```bash ```bash
# Use the Azure provider # Use the Azure provider
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.azure.AzureProvider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.azure.AzureDCRProvider
# Azure OAuth credentials # Azure OAuth credentials
FASTMCP_SERVER_AUTH_AZURE_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149 FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_ID=835f09b6-0f0f-40cc-85cb-f32c5829a149
FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET=your-client-secret-here FASTMCP_SERVER_AUTH_AZURE_DCR_CLIENT_SECRET=your-client-secret-here
FASTMCP_SERVER_AUTH_AZURE_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5 FASTMCP_SERVER_AUTH_AZURE_DCR_TENANT_ID=08541b6e-646d-43de-a0eb-834e6713d6d5
FASTMCP_SERVER_AUTH_AZURE_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_AZURE_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES=read,write FASTMCP_SERVER_AUTH_AZURE_DCR_REQUIRED_SCOPES=read,write
# Optional custom API configuration # Optional custom API configuration
# FASTMCP_SERVER_AUTH_AZURE_IDENTIFIER_URI=api://your-api-id # FASTMCP_SERVER_AUTH_AZURE_DCR_IDENTIFIER_URI=api://your-api-id
# Request additional upstream scopes (optional) # Request additional upstream scopes (optional)
# FASTMCP_SERVER_AUTH_AZURE_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read # FASTMCP_SERVER_AUTH_AZURE_DCR_ADDITIONAL_AUTHORIZE_SCOPES=User.Read,Mail.Read
``` ```
With environment variables set, your server code simplifies to: With environment variables set, your server code simplifies to:

View file

@ -43,7 +43,7 @@ Create an OAuth App in your GitHub settings to get the credentials needed for au
</Warning> </Warning>
<Tip> <Tip>
If you want to use a custom callback path (e.g., `/auth/github/callback`), make sure to set the same path in both your GitHub OAuth App settings and the `redirect_path` parameter when configuring the GitHubProvider. If you want to use a custom callback path (e.g., `/auth/github/callback`), make sure to set the same path in both your GitHub OAuth App settings and the `redirect_path` parameter when configuring the GitHubDCRProvider.
</Tip> </Tip>
</Step> </Step>
@ -61,14 +61,14 @@ Create an OAuth App in your GitHub settings to get the credentials needed for au
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `GitHubProvider`, which handles GitHub's OAuth quirks automatically: Create your FastMCP server using the `GitHubDCRProvider`, which handles GitHub's OAuth flow automatically:
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
# The GitHubProvider handles GitHub's token format and validation # The GitHubDCRProvider handles GitHub's token format and validation
auth_provider = GitHubProvider( auth_provider = GitHubDCRProvider(
client_id="Ov23liAbcDefGhiJkLmN", # Your GitHub OAuth App Client ID client_id="Ov23liAbcDefGhiJkLmN", # Your GitHub OAuth App Client ID
client_secret="github_pat_...", # Your GitHub OAuth App Client Secret client_secret="github_pat_...", # Your GitHub OAuth App Client Secret
base_url="http://localhost:8000", # Must match your OAuth App configuration base_url="http://localhost:8000", # Must match your OAuth App configuration
@ -84,7 +84,7 @@ async def get_user_info() -> dict:
from fastmcp.server.dependencies import get_access_token from fastmcp.server.dependencies import get_access_token
token = get_access_token() token = get_access_token()
# The GitHubProvider stores user data in token claims # The GitHubDCRProvider stores user data in token claims
return { return {
"github_user": token.claims.get("login"), "github_user": token.claims.get("login"),
"name": token.claims.get("name"), "name": token.claims.get("name"),
@ -147,7 +147,7 @@ Setting this environment variable allows the GitHub provider to be used automati
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.github.GitHubProvider` to use GitHub authentication. Set to `fastmcp.server.auth.providers.github.GitHubDCRProvider` to use GitHub authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -156,31 +156,31 @@ Set to `fastmcp.server.auth.providers.github.GitHubProvider` to use GitHub authe
These environment variables provide default values for the GitHub provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the GitHub provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID" required>
Your GitHub OAuth App Client ID (e.g., `Ov23liAbcDefGhiJkLmN`) Your GitHub OAuth App Client ID (e.g., `Ov23liAbcDefGhiJkLmN`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET" required>
Your GitHub OAuth App Client Secret Your GitHub OAuth App Client Secret
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_BASE_URL" default="http://localhost:8000"> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_BASE_URL" default="http://localhost:8000">
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_REDIRECT_PATH" default="/auth/callback">
Redirect path configured in your GitHub OAuth App Redirect path configured in your GitHub OAuth App
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES" default='["user"]'> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_REQUIRED_SCOPES" default='["user"]'>
Comma-, space-, or JSON-separated list of required GitHub scopes (e.g., `user repo` or `["user","repo"]`) Comma-, space-, or JSON-separated list of required GitHub scopes (e.g., `user repo` or `["user","repo"]`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_TIMEOUT_SECONDS" default="10"> <ParamField path="FASTMCP_SERVER_AUTH_GITHUB_DCR_TIMEOUT_SECONDS" default="10">
HTTP request timeout for GitHub API calls HTTP request timeout for GitHub API calls
</ParamField> </ParamField>
</Card> </Card>
@ -188,13 +188,13 @@ HTTP request timeout for GitHub API calls
Example `.env` file: Example `.env` file:
```bash ```bash
# Use the GitHub provider # Use the GitHub provider
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubDCRProvider
# GitHub OAuth credentials # GitHub OAuth credentials
FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID=Ov23liAbcDefGhiJkLmN FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID=Ov23liAbcDefGhiJkLmN
FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET=github_pat_... FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET=github_pat_...
FASTMCP_SERVER_AUTH_GITHUB_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_GITHUB_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES=user,repo FASTMCP_SERVER_AUTH_GITHUB_DCR_REQUIRED_SCOPES=user,repo
``` ```
With environment variables set, your server code simplifies to: With environment variables set, your server code simplifies to:

View file

@ -46,7 +46,7 @@ Create an OAuth 2.0 Client ID in your Google Cloud Console to get the credential
</Warning> </Warning>
<Tip> <Tip>
If you want to use a custom callback path (e.g., `/auth/google/callback`), make sure to set the same path in both your Google OAuth Client settings and the `redirect_path` parameter when configuring the GoogleProvider. If you want to use a custom callback path (e.g., `/auth/google/callback`), make sure to set the same path in both your Google OAuth Client settings and the `redirect_path` parameter when configuring the GoogleDCRProvider.
</Tip> </Tip>
</Step> </Step>
@ -66,14 +66,14 @@ Create an OAuth 2.0 Client ID in your Google Cloud Console to get the credential
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `GoogleProvider`, which handles Google's OAuth flow automatically: Create your FastMCP server using the `GoogleDCRProvider`, which handles Google's OAuth flow automatically:
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.google import GoogleProvider from fastmcp.server.auth.providers.google import GoogleDCRProvider
# The GoogleProvider handles Google's token format and validation # The GoogleDCRProvider handles Google's token format and validation
auth_provider = GoogleProvider( auth_provider = GoogleDCRProvider(
client_id="123456789.apps.googleusercontent.com", # Your Google OAuth Client ID client_id="123456789.apps.googleusercontent.com", # Your Google OAuth Client ID
client_secret="GOCSPX-abc123...", # Your Google OAuth Client Secret client_secret="GOCSPX-abc123...", # Your Google OAuth Client Secret
base_url="http://localhost:8000", # Must match your OAuth configuration base_url="http://localhost:8000", # Must match your OAuth configuration
@ -93,7 +93,7 @@ async def get_user_info() -> dict:
from fastmcp.server.dependencies import get_access_token from fastmcp.server.dependencies import get_access_token
token = get_access_token() token = get_access_token()
# The GoogleProvider stores user data in token claims # The GoogleDCRProvider stores user data in token claims
return { return {
"google_id": token.claims.get("sub"), "google_id": token.claims.get("sub"),
"email": token.claims.get("email"), "email": token.claims.get("email"),
@ -160,7 +160,7 @@ Setting this environment variable allows the Google provider to be used automati
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.google.GoogleProvider` to use Google authentication. Set to `fastmcp.server.auth.providers.google.GoogleDCRProvider` to use Google authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -169,31 +169,31 @@ Set to `fastmcp.server.auth.providers.google.GoogleProvider` to use Google authe
These environment variables provide default values for the Google provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the Google provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_ID" required>
Your Google OAuth 2.0 Client ID (e.g., `123456789.apps.googleusercontent.com`) Your Google OAuth 2.0 Client ID (e.g., `123456789.apps.googleusercontent.com`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_SECRET" required>
Your Google OAuth 2.0 Client Secret (e.g., `GOCSPX-abc123...`) Your Google OAuth 2.0 Client Secret (e.g., `GOCSPX-abc123...`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL" default="http://localhost:8000"> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_BASE_URL" default="http://localhost:8000">
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_REDIRECT_PATH" default="/auth/callback">
Redirect path configured in your Google OAuth Client Redirect path configured in your Google OAuth Client
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES" default="[]"> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_REQUIRED_SCOPES" default="[]">
Comma-, space-, or JSON-separated list of required Google scopes (e.g., `"openid,https://www.googleapis.com/auth/userinfo.email"` or `["openid", "https://www.googleapis.com/auth/userinfo.email"]`) Comma-, space-, or JSON-separated list of required Google scopes (e.g., `"openid,https://www.googleapis.com/auth/userinfo.email"` or `["openid", "https://www.googleapis.com/auth/userinfo.email"]`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_TIMEOUT_SECONDS" default="10"> <ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_DCR_TIMEOUT_SECONDS" default="10">
HTTP request timeout for Google API calls HTTP request timeout for Google API calls
</ParamField> </ParamField>
</Card> </Card>
@ -201,13 +201,13 @@ HTTP request timeout for Google API calls
Example `.env` file: Example `.env` file:
```bash ```bash
# Use the Google provider # Use the Google provider
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleDCRProvider
# Google OAuth credentials # Google OAuth credentials
FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_ID=123456789.apps.googleusercontent.com
FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-abc123... FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_SECRET=GOCSPX-abc123...
FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_GOOGLE_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES=openid,https://www.googleapis.com/auth/userinfo.email FASTMCP_SERVER_AUTH_GOOGLE_DCR_REQUIRED_SCOPES=openid,https://www.googleapis.com/auth/userinfo.email
``` ```
With environment variables set, your server code simplifies to: With environment variables set, your server code simplifies to:

View file

@ -57,14 +57,14 @@ The callback URL must match exactly. The default path is `/auth/callback`, but y
### Step 2: FastMCP Configuration ### Step 2: FastMCP Configuration
Create your FastMCP server using the `WorkOSProvider`: Create your FastMCP server using the `WorkOSDCRProvider`:
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import WorkOSProvider from fastmcp.server.auth.providers.workos import WorkOSDCRProvider
# Configure WorkOS OAuth # Configure WorkOS OAuth
auth = WorkOSProvider( auth = WorkOSDCRProvider(
client_id="client_YOUR_CLIENT_ID", client_id="client_YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET", client_secret="YOUR_CLIENT_SECRET",
authkit_domain="https://your-app.authkit.app", authkit_domain="https://your-app.authkit.app",
@ -138,7 +138,7 @@ Setting this environment variable allows the WorkOS provider to be used automati
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set"> <ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
Set to `fastmcp.server.auth.providers.workos.WorkOSProvider` to use WorkOS authentication. Set to `fastmcp.server.auth.providers.workos.WorkOSDCRProvider` to use WorkOS authentication.
</ParamField> </ParamField>
</Card> </Card>
@ -147,35 +147,35 @@ Set to `fastmcp.server.auth.providers.workos.WorkOSProvider` to use WorkOS authe
These environment variables provide default values for the WorkOS provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`. These environment variables provide default values for the WorkOS provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
<Card> <Card>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID" required> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_ID" required>
Your WorkOS OAuth App Client ID (e.g., `client_01K33Y6GGS7T3AWMPJWKW42Y3Q`) Your WorkOS OAuth App Client ID (e.g., `client_01K33Y6GGS7T3AWMPJWKW42Y3Q`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET" required> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_SECRET" required>
Your WorkOS OAuth App Client Secret Your WorkOS OAuth App Client Secret
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN" required> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_AUTHKIT_DOMAIN" required>
Your WorkOS AuthKit domain (e.g., `https://your-app.authkit.app`) Your WorkOS AuthKit domain (e.g., `https://your-app.authkit.app`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_BASE_URL" default="http://localhost:8000"> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_BASE_URL" default="http://localhost:8000">
Public URL where OAuth endpoints will be accessible (includes any mount path) Public URL where OAuth endpoints will be accessible (includes any mount path)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_ISSUER_URL" default="Uses BASE_URL"> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_ISSUER_URL" default="Uses BASE_URL">
Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details. Issuer URL for OAuth metadata (defaults to `BASE_URL`). Set to root-level URL when mounting under a path prefix to avoid 404 logs. See [HTTP Deployment guide](/deployment/http#mounting-authenticated-servers) for details.
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_REDIRECT_PATH" default="/auth/callback"> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_REDIRECT_PATH" default="/auth/callback">
Redirect path configured in your WorkOS OAuth App Redirect path configured in your WorkOS OAuth App
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES" default="[]"> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_REQUIRED_SCOPES" default="[]">
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid","profile","email"]`) Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid","profile","email"]`)
</ParamField> </ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_TIMEOUT_SECONDS" default="10"> <ParamField path="FASTMCP_SERVER_AUTH_WORKOS_DCR_TIMEOUT_SECONDS" default="10">
HTTP request timeout for WorkOS API calls HTTP request timeout for WorkOS API calls
</ParamField> </ParamField>
</Card> </Card>
@ -183,14 +183,14 @@ HTTP request timeout for WorkOS API calls
Example `.env` file: Example `.env` file:
```bash ```bash
# WorkOS OAuth credentials (always used as defaults) # WorkOS OAuth credentials (always used as defaults)
FASTMCP_SERVER_AUTH_WORKOS_CLIENT_ID=client_01K33Y6GGS7T3AWMPJWKW42Y3Q FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_ID=client_01K33Y6GGS7T3AWMPJWKW42Y3Q
FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET=your_client_secret FASTMCP_SERVER_AUTH_WORKOS_DCR_CLIENT_SECRET=your_client_secret
FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN=https://your-app.authkit.app FASTMCP_SERVER_AUTH_WORKOS_DCR_AUTHKIT_DOMAIN=https://your-app.authkit.app
FASTMCP_SERVER_AUTH_WORKOS_BASE_URL=https://your-server.com FASTMCP_SERVER_AUTH_WORKOS_DCR_BASE_URL=https://your-server.com
FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES=["openid","profile","email"] FASTMCP_SERVER_AUTH_WORKOS_DCR_REQUIRED_SCOPES=["openid","profile","email"]
# Optional: Automatically provision WorkOS auth for all servers # Optional: Automatically provision WorkOS auth for all servers
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSDCRProvider
``` ```
With environment variables set, you can either: With environment variables set, you can either:
@ -198,14 +198,14 @@ With environment variables set, you can either:
**Option 1: Manual instantiation (env vars provide defaults)** **Option 1: Manual instantiation (env vars provide defaults)**
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.workos import WorkOSProvider from fastmcp.server.auth.providers.workos import WorkOSDCRProvider
# Env vars provide default values for WorkOSProvider() # Env vars provide default values for WorkOSDCRProvider()
auth = WorkOSProvider() # Uses env var defaults auth = WorkOSDCRProvider() # Uses env var defaults
mcp = FastMCP(name="WorkOS Protected Server", auth=auth) mcp = FastMCP(name="WorkOS Protected Server", auth=auth)
``` ```
**Option 2: Automatic provisioning (requires FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSProvider)** **Option 2: Automatic provisioning (requires FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.WorkOSDCRProvider)**
```python server.py ```python server.py
from fastmcp import FastMCP from fastmcp import FastMCP

View file

@ -0,0 +1,404 @@
---
title: oauth_dcr_proxy
sidebarTitle: oauth_dcr_proxy
---
# `fastmcp.server.auth.oauth_dcr_proxy`
OAuth Proxy Provider for FastMCP.
This provider acts as a transparent proxy to an upstream OAuth Authorization Server,
handling Dynamic Client Registration locally while forwarding all other OAuth flows.
This enables authentication with upstream providers that don't support DCR or have
restricted client registration policies.
Key features:
- Proxies authorization and token endpoints to upstream server
- Implements local Dynamic Client Registration with fixed upstream credentials
- Validates tokens using upstream JWKS
- Maintains minimal local state for bookkeeping
- Enhanced logging with request correlation
This implementation is based on the OAuth 2.1 specification and is designed for
production use with enterprise identity providers.
## Functions
### `create_consent_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Authorization Consent', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None) -> str
```
Create a styled HTML consent page for OAuth authorization requests.
## Classes
### `OAuthTransaction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L102" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OAuth transaction state for consent flow.
Stored server-side to track active authorization flows with client context.
Includes CSRF tokens for consent protection per MCP security best practices.
### `ClientCode` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Client authorization code with PKCE and upstream tokens.
Stored server-side after upstream IdP callback. Contains the upstream
tokens bound to the client's PKCE challenge for secure token exchange.
### `UpstreamTokenSet` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Stored upstream OAuth tokens from identity provider.
These tokens are obtained from the upstream provider (Google, GitHub, etc.)
and are stored encrypted at rest. They are never exposed to MCP clients.
### `JTIMapping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Maps FastMCP token JTI to upstream token ID.
This allows stateless JWT validation while still being able to look up
the corresponding upstream token when tools need to access upstream APIs.
### `ProxyDCRClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Client for DCR proxy with configurable redirect URI validation.
This special client class is critical for the OAuth proxy to work correctly
with Dynamic Client Registration (DCR). Here's why it exists:
Problem:
--------
When MCP clients use OAuth, they dynamically register with random localhost
ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
1. Accept these dynamic redirect URIs from clients based on configured patterns
2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
3. Forward the authorization code back to the client's dynamic URI
Solution:
---------
This class validates redirect URIs against configurable patterns,
while the proxy internally uses its own fixed redirect URI with the upstream
provider. This allows the flow to work even when clients reconnect with
different ports or when tokens are cached.
Without proper validation, clients could get "Redirect URI not registered" errors
when trying to authenticate with cached tokens, or security vulnerabilities could
arise from accepting arbitrary redirect URIs.
**Methods:**
#### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L203" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
```
Validate redirect URI against allowed patterns.
Since we're acting as a proxy and clients register dynamically,
we validate their redirect URIs against configurable patterns.
This is essential for cached token scenarios where the client may
reconnect with a different port.
### `TokenHandler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
TokenHandler that returns OAuth 2.1 compliant error responses.
The MCP SDK always returns HTTP 400 for all client authentication issues.
However, OAuth 2.1 Section 5.3 and the MCP specification require that
invalid or expired tokens MUST receive a HTTP 401 response.
This handler extends the base MCP SDK TokenHandler to transform client
authentication failures into OAuth 2.1 compliant responses:
- Changes 'unauthorized_client' to 'invalid_client' error code
- Returns HTTP 401 status code instead of 400 for client auth failures
Per OAuth 2.1 Section 5.3: "The authorization server MAY return an HTTP 401
(Unauthorized) status code to indicate which HTTP authentication schemes
are supported."
Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response."
**Methods:**
#### `response` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
response(self, obj: TokenSuccessResponse | TokenErrorResponse)
```
Override response method to provide OAuth 2.1 compliant error handling.
### `OAuthDCRProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L393" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
Purpose
-------
MCP clients expect OAuth providers to support Dynamic Client Registration (DCR),
where clients can register themselves dynamically and receive unique credentials.
Most enterprise IDPs (Google, GitHub, Azure AD, etc.) don't support DCR and require
pre-registered OAuth applications with fixed credentials.
This proxy bridges that gap by:
- Presenting a full DCR-compliant OAuth interface to MCP clients
- Translating DCR registration requests to use pre-configured upstream credentials
- Proxying all OAuth flows to the upstream IDP with appropriate translations
- Managing the state and security requirements of both protocols
Architecture Overview
--------------------
The proxy maintains a single OAuth app registration with the upstream provider
while allowing unlimited MCP clients to register and authenticate dynamically.
It implements the complete OAuth 2.1 + DCR specification for clients while
translating to whatever OAuth variant the upstream provider requires.
Key Translation Challenges Solved
---------------------------------
1. Dynamic Client Registration:
- MCP clients expect to register dynamically and get unique credentials
- Upstream IDPs require pre-registered apps with fixed credentials
- Solution: Accept DCR requests, return shared upstream credentials
2. Dynamic Redirect URIs:
- MCP clients use random localhost ports that change between sessions
- Upstream IDPs require fixed, pre-registered redirect URIs
- Solution: Use proxy's fixed callback URL with upstream, forward to client's dynamic URI
3. Authorization Code Mapping:
- Upstream returns codes for the proxy's redirect URI
- Clients expect codes for their own redirect URIs
- Solution: Exchange upstream code server-side, issue new code to client
4. State Parameter Collision:
- Both client and proxy need to maintain state through the flow
- Only one state parameter available in OAuth
- Solution: Use transaction ID as state with upstream, preserve client's state
5. Token Management:
- Clients may expect different token formats/claims than upstream provides
- Need to track tokens for revocation and refresh
- Solution: Store token relationships, forward upstream tokens transparently
OAuth Flow Implementation
------------------------
1. Client Registration (DCR):
- Accept any client registration request
- Store ProxyDCRClient that accepts dynamic redirect URIs
2. Authorization:
- Store transaction mapping client details to proxy flow
- Redirect to upstream with proxy's fixed redirect URI
- Use transaction ID as state parameter with upstream
3. Upstream Callback:
- Exchange upstream authorization code for tokens (server-side)
- Generate new authorization code bound to client's PKCE challenge
- Redirect to client's original dynamic redirect URI
4. Token Exchange:
- Validate client's code and PKCE verifier
- Return previously obtained upstream tokens
- Clean up one-time use authorization code
5. Token Refresh:
- Forward refresh requests to upstream using authlib
- Handle token rotation if upstream issues new refresh token
- Update local token mappings
State Management
---------------
The proxy maintains minimal but crucial state:
- _oauth_transactions: Active authorization flows with client context
- _client_codes: Authorization codes with PKCE challenges and upstream tokens
- _access_tokens, _refresh_tokens: Token storage for revocation
- Token relationship mappings for cleanup and rotation
Security Considerations
----------------------
- PKCE enforced end-to-end (client to proxy, proxy to upstream)
- Authorization codes are single-use with short expiry
- Transaction IDs are cryptographically random
- All state is cleaned up after use to prevent replay
- Token validation delegates to upstream provider
Provider Compatibility
---------------------
Works with any OAuth 2.0 provider that supports:
- Authorization code flow
- Fixed redirect URI (configured in provider's app settings)
- Standard token endpoint
Handles provider-specific requirements:
- Google: Ensures minimum scope requirements
- GitHub: Compatible with OAuth Apps and GitHub Apps
- Azure AD: Handles tenant-specific endpoints
- Generic: Works with any spec-compliant provider
**Methods:**
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L822" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_client(self, client_id: str) -> OAuthClientInformationFull | None
```
Get client information by ID. This is generally the random ID
provided to the DCR client during registration, not the upstream client ID.
For unregistered clients, returns None (which will raise an error in the SDK).
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L837" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
register_client(self, client_info: OAuthClientInformationFull) -> None
```
Register a client locally
When a client registers, we create a ProxyDCRClient that is more
forgiving about validating redirect URIs, since the DCR client's
redirect URI will likely be localhost or unknown to the proxied IDP. The
proxied IDP only knows about this server's fixed redirect URI.
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L883" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
```
Start OAuth transaction and route through consent interstitial.
Flow:
1. Store transaction with client details and PKCE (if forwarding)
2. Return local /consent URL; browser visits consent first
3. Consent handler redirects to upstream IdP if approved/already approved
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L940" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
```
Load authorization code for validation.
Look up our client code and return authorization code object
with PKCE challenge for validation.
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L982" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
```
Exchange authorization code for FastMCP-issued tokens.
Implements the token factory pattern:
1. Retrieves upstream tokens from stored authorization code
2. Extracts user identity from upstream token
3. Encrypts and stores upstream tokens
4. Issues FastMCP-signed JWT tokens
5. Returns FastMCP tokens (NOT upstream tokens)
PKCE validation is handled by the MCP framework before this method is called.
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L1152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
```
Load refresh token from local storage.
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L1160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
```
Exchange FastMCP refresh token for new FastMCP access token.
Implements two-tier refresh:
1. Verify FastMCP refresh token
2. Look up upstream token via JTI mapping
3. Refresh upstream token with upstream provider
4. Update stored upstream token
5. Issue new FastMCP access token
6. Keep same FastMCP refresh token (unless upstream rotates)
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L1374" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_access_token(self, token: str) -> AccessToken | None
```
Validate FastMCP JWT by swapping for upstream token.
This implements the token swap pattern:
1. Verify FastMCP JWT signature (proves it's our token)
2. Look up upstream token via JTI mapping
3. Decrypt upstream token
4. Validate upstream token with provider (GitHub API, JWT validation, etc.)
5. Return upstream validation result
The FastMCP JWT is a reference token - all authorization data comes
from validating the upstream token via the TokenVerifier.
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L1438" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
revoke_token(self, token: AccessToken | RefreshToken) -> None
```
Revoke token locally and with upstream server if supported.
Removes tokens from local storage and attempts to revoke them with
the upstream server if a revocation endpoint is configured.
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_dcr_proxy.py#L1482" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
```
Get OAuth routes with custom proxy token handler.
This method creates standard OAuth routes and replaces the token endpoint
with our proxy handler that forwards requests to the upstream OAuth server.
**Args:**
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
This is used to advertise the resource URL in metadata.

View file

@ -6,399 +6,8 @@ sidebarTitle: oauth_proxy
# `fastmcp.server.auth.oauth_proxy` # `fastmcp.server.auth.oauth_proxy`
OAuth Proxy Provider for FastMCP. Backwards compatibility shim for oauth_proxy.py
This provider acts as a transparent proxy to an upstream OAuth Authorization Server, The OauthProxy class has been moved to fastmcp.server.auth.oauth_dcr_proxy.OAuthDCRProxy
handling Dynamic Client Registration locally while forwarding all other OAuth flows. for better organization. This module provides a backwards-compatible import.
This enables authentication with upstream providers that don't support DCR or have
restricted client registration policies.
Key features:
- Proxies authorization and token endpoints to upstream server
- Implements local Dynamic Client Registration with fixed upstream credentials
- Validates tokens using upstream JWKS
- Maintains minimal local state for bookkeeping
- Enhanced logging with request correlation
This implementation is based on the OAuth 2.1 specification and is designed for
production use with enterprise identity providers.
## Functions
### `create_consent_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id: str, csrf_token: str, client_name: str | None = None, title: str = 'Authorization Consent', server_name: str | None = None, server_icon_url: str | None = None, server_website_url: str | None = None) -> str
```
Create a styled HTML consent page for OAuth authorization requests.
## Classes
### `OAuthTransaction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L102" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OAuth transaction state for consent flow.
Stored server-side to track active authorization flows with client context.
Includes CSRF tokens for consent protection per MCP security best practices.
### `ClientCode` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Client authorization code with PKCE and upstream tokens.
Stored server-side after upstream IdP callback. Contains the upstream
tokens bound to the client's PKCE challenge for secure token exchange.
### `UpstreamTokenSet` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Stored upstream OAuth tokens from identity provider.
These tokens are obtained from the upstream provider (Google, GitHub, etc.)
and are stored encrypted at rest. They are never exposed to MCP clients.
### `JTIMapping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Maps FastMCP token JTI to upstream token ID.
This allows stateless JWT validation while still being able to look up
the corresponding upstream token when tools need to access upstream APIs.
### `ProxyDCRClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Client for DCR proxy with configurable redirect URI validation.
This special client class is critical for the OAuth proxy to work correctly
with Dynamic Client Registration (DCR). Here's why it exists:
Problem:
--------
When MCP clients use OAuth, they dynamically register with random localhost
ports (e.g., http://localhost:55454/callback). The OAuth proxy needs to:
1. Accept these dynamic redirect URIs from clients based on configured patterns
2. Use its own fixed redirect URI with the upstream provider (Google, GitHub, etc.)
3. Forward the authorization code back to the client's dynamic URI
Solution:
---------
This class validates redirect URIs against configurable patterns,
while the proxy internally uses its own fixed redirect URI with the upstream
provider. This allows the flow to work even when clients reconnect with
different ports or when tokens are cached.
Without proper validation, clients could get "Redirect URI not registered" errors
when trying to authenticate with cached tokens, or security vulnerabilities could
arise from accepting arbitrary redirect URIs.
**Methods:**
#### `validate_redirect_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L203" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl
```
Validate redirect URI against allowed patterns.
Since we're acting as a proxy and clients register dynamically,
we validate their redirect URIs against configurable patterns.
This is essential for cached token scenarios where the client may
reconnect with a different port.
### `TokenHandler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
TokenHandler that returns OAuth 2.1 compliant error responses.
The MCP SDK always returns HTTP 400 for all client authentication issues.
However, OAuth 2.1 Section 5.3 and the MCP specification require that
invalid or expired tokens MUST receive a HTTP 401 response.
This handler extends the base MCP SDK TokenHandler to transform client
authentication failures into OAuth 2.1 compliant responses:
- Changes 'unauthorized_client' to 'invalid_client' error code
- Returns HTTP 401 status code instead of 400 for client auth failures
Per OAuth 2.1 Section 5.3: "The authorization server MAY return an HTTP 401
(Unauthorized) status code to indicate which HTTP authentication schemes
are supported."
Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response."
**Methods:**
#### `response` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
response(self, obj: TokenSuccessResponse | TokenErrorResponse)
```
Override response method to provide OAuth 2.1 compliant error handling.
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L393" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
Purpose
-------
MCP clients expect OAuth providers to support Dynamic Client Registration (DCR),
where clients can register themselves dynamically and receive unique credentials.
Most enterprise IDPs (Google, GitHub, Azure AD, etc.) don't support DCR and require
pre-registered OAuth applications with fixed credentials.
This proxy bridges that gap by:
- Presenting a full DCR-compliant OAuth interface to MCP clients
- Translating DCR registration requests to use pre-configured upstream credentials
- Proxying all OAuth flows to the upstream IDP with appropriate translations
- Managing the state and security requirements of both protocols
Architecture Overview
--------------------
The proxy maintains a single OAuth app registration with the upstream provider
while allowing unlimited MCP clients to register and authenticate dynamically.
It implements the complete OAuth 2.1 + DCR specification for clients while
translating to whatever OAuth variant the upstream provider requires.
Key Translation Challenges Solved
---------------------------------
1. Dynamic Client Registration:
- MCP clients expect to register dynamically and get unique credentials
- Upstream IDPs require pre-registered apps with fixed credentials
- Solution: Accept DCR requests, return shared upstream credentials
2. Dynamic Redirect URIs:
- MCP clients use random localhost ports that change between sessions
- Upstream IDPs require fixed, pre-registered redirect URIs
- Solution: Use proxy's fixed callback URL with upstream, forward to client's dynamic URI
3. Authorization Code Mapping:
- Upstream returns codes for the proxy's redirect URI
- Clients expect codes for their own redirect URIs
- Solution: Exchange upstream code server-side, issue new code to client
4. State Parameter Collision:
- Both client and proxy need to maintain state through the flow
- Only one state parameter available in OAuth
- Solution: Use transaction ID as state with upstream, preserve client's state
5. Token Management:
- Clients may expect different token formats/claims than upstream provides
- Need to track tokens for revocation and refresh
- Solution: Store token relationships, forward upstream tokens transparently
OAuth Flow Implementation
------------------------
1. Client Registration (DCR):
- Accept any client registration request
- Store ProxyDCRClient that accepts dynamic redirect URIs
2. Authorization:
- Store transaction mapping client details to proxy flow
- Redirect to upstream with proxy's fixed redirect URI
- Use transaction ID as state parameter with upstream
3. Upstream Callback:
- Exchange upstream authorization code for tokens (server-side)
- Generate new authorization code bound to client's PKCE challenge
- Redirect to client's original dynamic redirect URI
4. Token Exchange:
- Validate client's code and PKCE verifier
- Return previously obtained upstream tokens
- Clean up one-time use authorization code
5. Token Refresh:
- Forward refresh requests to upstream using authlib
- Handle token rotation if upstream issues new refresh token
- Update local token mappings
State Management
---------------
The proxy maintains minimal but crucial state:
- _oauth_transactions: Active authorization flows with client context
- _client_codes: Authorization codes with PKCE challenges and upstream tokens
- _access_tokens, _refresh_tokens: Token storage for revocation
- Token relationship mappings for cleanup and rotation
Security Considerations
----------------------
- PKCE enforced end-to-end (client to proxy, proxy to upstream)
- Authorization codes are single-use with short expiry
- Transaction IDs are cryptographically random
- All state is cleaned up after use to prevent replay
- Token validation delegates to upstream provider
Provider Compatibility
---------------------
Works with any OAuth 2.0 provider that supports:
- Authorization code flow
- Fixed redirect URI (configured in provider's app settings)
- Standard token endpoint
Handles provider-specific requirements:
- Google: Ensures minimum scope requirements
- GitHub: Compatible with OAuth Apps and GitHub Apps
- Azure AD: Handles tenant-specific endpoints
- Generic: Works with any spec-compliant provider
**Methods:**
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L822" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_client(self, client_id: str) -> OAuthClientInformationFull | None
```
Get client information by ID. This is generally the random ID
provided to the DCR client during registration, not the upstream client ID.
For unregistered clients, returns None (which will raise an error in the SDK).
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L837" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
register_client(self, client_info: OAuthClientInformationFull) -> None
```
Register a client locally
When a client registers, we create a ProxyDCRClient that is more
forgiving about validating redirect URIs, since the DCR client's
redirect URI will likely be localhost or unknown to the proxied IDP. The
proxied IDP only knows about this server's fixed redirect URI.
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L883" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
```
Start OAuth transaction and route through consent interstitial.
Flow:
1. Store transaction with client details and PKCE (if forwarding)
2. Return local /consent URL; browser visits consent first
3. Consent handler redirects to upstream IdP if approved/already approved
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L940" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
```
Load authorization code for validation.
Look up our client code and return authorization code object
with PKCE challenge for validation.
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L982" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
```
Exchange authorization code for FastMCP-issued tokens.
Implements the token factory pattern:
1. Retrieves upstream tokens from stored authorization code
2. Extracts user identity from upstream token
3. Encrypts and stores upstream tokens
4. Issues FastMCP-signed JWT tokens
5. Returns FastMCP tokens (NOT upstream tokens)
PKCE validation is handled by the MCP framework before this method is called.
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
```
Load refresh token from local storage.
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
```
Exchange FastMCP refresh token for new FastMCP access token.
Implements two-tier refresh:
1. Verify FastMCP refresh token
2. Look up upstream token via JTI mapping
3. Refresh upstream token with upstream provider
4. Update stored upstream token
5. Issue new FastMCP access token
6. Keep same FastMCP refresh token (unless upstream rotates)
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1374" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_access_token(self, token: str) -> AccessToken | None
```
Validate FastMCP JWT by swapping for upstream token.
This implements the token swap pattern:
1. Verify FastMCP JWT signature (proves it's our token)
2. Look up upstream token via JTI mapping
3. Decrypt upstream token
4. Validate upstream token with provider (GitHub API, JWT validation, etc.)
5. Return upstream validation result
The FastMCP JWT is a reference token - all authorization data comes
from validating the upstream token via the TokenVerifier.
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1438" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
revoke_token(self, token: AccessToken | RefreshToken) -> None
```
Revoke token locally and with upstream server if supported.
Removes tokens from local storage and attempts to revoke them with
the upstream server if a revocation endpoint is configured.
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1482" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_routes(self, mcp_path: str | None = None) -> list[Route]
```
Get OAuth routes with custom proxy token handler.
This method creates standard OAuth routes and replaces the token endpoint
with our proxy handler that forwards requests to the upstream OAuth server.
**Args:**
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
This is used to advertise the resource URL in metadata.

View file

@ -0,0 +1,82 @@
---
title: oidc_dcr_proxy
sidebarTitle: oidc_dcr_proxy
---
# `fastmcp.server.auth.oidc_dcr_proxy`
OIDC Proxy Provider for FastMCP.
This provider acts as a transparent proxy to an upstream OIDC compliant Authorization
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and
forwarding of all OAuth flows.
This implementation is based on:
OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
## Classes
### `OIDCConfiguration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_dcr_proxy.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OIDC Configuration.
**Methods:**
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_dcr_proxy.py#L142" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_oidc_configuration(cls, config_url: AnyHttpUrl) -> Self
```
Get the OIDC configuration for the specified config URL.
**Args:**
- `config_url`: The OIDC config URL
- `strict`: The strict flag for the configuration
- `timeout_seconds`: HTTP request timeout in seconds
### `OIDCDCRProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_dcr_proxy.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OAuth provider that wraps OAuthDCRProxy to provide configuration via an OIDC configuration URL.
This provider makes it easier to add OAuth protection for any upstream provider
that is OIDC compliant.
**Methods:**
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_dcr_proxy.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration
```
Gets the OIDC configuration for the specified configuration URL.
**Args:**
- `config_url`: The OIDC configuration URL
- `strict`: The strict flag for the configuration
- `timeout_seconds`: HTTP request timeout in seconds
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_dcr_proxy.py#L328" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_token_verifier(self) -> TokenVerifier
```
Creates the token verifier for the specified OIDC configuration and arguments.
**Args:**
- `algorithm`: Optional token verifier algorithm
- `audience`: Optional token verifier audience
- `required_scopes`: Optional token verifier required_scopes
- `timeout_seconds`: HTTP request timeout in seconds

View file

@ -6,77 +6,8 @@ sidebarTitle: oidc_proxy
# `fastmcp.server.auth.oidc_proxy` # `fastmcp.server.auth.oidc_proxy`
OIDC Proxy Provider for FastMCP. Backwards compatibility shim for oidc_proxy.py
This provider acts as a transparent proxy to an upstream OIDC compliant Authorization The OIDCProxy class has been moved to fastmcp.server.auth.oidc_dcr_proxy.OIDCDCRProxy
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and for better organization. This module provides a backwards-compatible import.
forwarding of all OAuth flows.
This implementation is based on:
OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
## Classes
### `OIDCConfiguration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
OIDC Configuration.
**Methods:**
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L142" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_oidc_configuration(cls, config_url: AnyHttpUrl) -> Self
```
Get the OIDC configuration for the specified config URL.
**Args:**
- `config_url`: The OIDC config URL
- `strict`: The strict flag for the configuration
- `timeout_seconds`: HTTP request timeout in seconds
### `OIDCProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
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
that is OIDC compliant.
**Methods:**
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration
```
Gets the OIDC configuration for the specified configuration URL.
**Args:**
- `config_url`: The OIDC configuration URL
- `strict`: The strict flag for the configuration
- `timeout_seconds`: HTTP request timeout in seconds
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L328" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_token_verifier(self) -> TokenVerifier
```
Creates the token verifier for the specified OIDC configuration and arguments.
**Args:**
- `algorithm`: Optional token verifier algorithm
- `audience`: Optional token verifier audience
- `required_scopes`: Optional token verifier required_scopes
- `timeout_seconds`: HTTP request timeout in seconds

View file

@ -14,10 +14,10 @@ just the configuration URL, client ID, client secret, audience, and base URL.
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# Simple Auth0 OAuth protection # Simple Auth0 OAuth protection
auth = Auth0Provider( auth = Auth0DCRProvider(
config_url="https://auth0.config.url", config_url="https://auth0.config.url",
client_id="your-auth0-client-id", client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret", client_secret="your-auth0-client-secret",
@ -31,17 +31,33 @@ Example:
## Classes ## Classes
### `Auth0ProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L37" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `Auth0DCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L44" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for Auth0 OIDC provider. Settings for Auth0 OIDC DCR provider.
### `Auth0Provider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `Auth0DCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
An Auth0 provider implementation for FastMCP. An Auth0 DCR provider implementation for FastMCP.
This provider is a complete Auth0 integration that's ready to use with 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. just the configuration URL, client ID, client secret, audience, and base URL.
### `Auth0Provider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/auth0.py#L208" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use Auth0DCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.

View file

@ -31,13 +31,21 @@ Example:
## Classes ## Classes
### `AWSCognitoProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `AWSCognitoDCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for AWS Cognito OAuth provider. Settings for AWS Cognito OAuth DCR provider.
### `AWSCognitoTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `AWSCognitoTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier that filters claims to Cognito-specific subset. Token verifier that filters claims to Cognito-specific subset.
@ -45,7 +53,7 @@ Token verifier that filters claims to Cognito-specific subset.
**Methods:** **Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
verify_token(self, token: str) -> AccessToken | None verify_token(self, token: str) -> AccessToken | None
@ -54,10 +62,10 @@ verify_token(self, token: str) -> AccessToken | None
Verify token and filter claims to Cognito-specific subset. Verify token and filter claims to Cognito-specific subset.
### `AWSCognitoProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `AWSCognitoDCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete AWS Cognito OAuth provider for FastMCP. Complete AWS Cognito OAuth DCR provider for FastMCP.
This provider makes it trivial to add AWS Cognito OAuth protection to any 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, FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
@ -70,9 +78,17 @@ Features:
- Support for Cognito User Pools - Support for Cognito User Pools
### `AWSCognitoProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use AWSCognitoDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
**Methods:** **Methods:**
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L226" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L274" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
get_token_verifier(self) -> TokenVerifier get_token_verifier(self) -> TokenVerifier

View file

@ -14,16 +14,24 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
## Classes ## Classes
### `AzureProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `AzureDCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for Azure OAuth provider. Settings for Azure OAuth DCR provider.
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L60" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `AzureDCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L83" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Azure (Microsoft Entra) OAuth provider for FastMCP. Azure (Microsoft Entra) OAuth DCR provider for FastMCP.
This provider implements Azure/Microsoft Entra ID authentication using the This provider implements Azure/Microsoft Entra ID authentication using the
OAuth Proxy pattern. It supports both organizational accounts and personal OAuth Proxy pattern. It supports both organizational accounts and personal
@ -43,9 +51,17 @@ Setup:
6. Get Application (client) ID, Directory (tenant) ID, and client secret 6. Get Application (client) ID, Directory (tenant) ID, and client secret
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use AzureDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
**Methods:** **Methods:**
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L228" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L274" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str

View file

@ -15,10 +15,10 @@ GitHub's OAuth flow, token validation, and user management.
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
# Simple GitHub OAuth protection # Simple GitHub OAuth protection
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-github-client-id", client_id="your-github-client-id",
client_secret="your-github-client-secret" client_secret="your-github-client-secret"
) )
@ -29,13 +29,21 @@ Example:
## Classes ## Classes
### `GitHubProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `GitHubDCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for GitHub OAuth provider. Settings for GitHub OAuth DCR provider.
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L88" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for GitHub OAuth tokens. Token verifier for GitHub OAuth tokens.
@ -46,7 +54,7 @@ by calling GitHub's API to check if they're valid and get user info.
**Methods:** **Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
verify_token(self, token: str) -> AccessToken | None verify_token(self, token: str) -> AccessToken | None
@ -55,10 +63,10 @@ verify_token(self, token: str) -> AccessToken | None
Verify GitHub OAuth token by calling GitHub API. Verify GitHub OAuth token by calling GitHub API.
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L169" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `GitHubDCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L193" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete GitHub OAuth provider for FastMCP. Complete GitHub OAuth DCR provider for FastMCP.
This provider makes it trivial to add GitHub OAuth protection to any This provider makes it trivial to add GitHub OAuth protection to any
FastMCP server. Just provide your GitHub OAuth app credentials and FastMCP server. Just provide your GitHub OAuth app credentials and
@ -70,3 +78,11 @@ Features:
- User information extraction - User information extraction
- Minimal configuration required - Minimal configuration required
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L320" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use GitHubDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.

View file

@ -15,10 +15,10 @@ Google's OAuth flow, token validation, and user management.
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.google import GoogleProvider from fastmcp.server.auth.providers.google import GoogleDCRProvider
# Simple Google OAuth protection # Simple Google OAuth protection
auth = GoogleProvider( auth = GoogleDCRProvider(
client_id="your-google-client-id.apps.googleusercontent.com", client_id="your-google-client-id.apps.googleusercontent.com",
client_secret="your-google-client-secret" client_secret="your-google-client-secret"
) )
@ -29,13 +29,21 @@ Example:
## Classes ## Classes
### `GoogleProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `GoogleDCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L48" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for Google OAuth provider. Settings for Google OAuth DCR provider.
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L66" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L59" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L89" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for Google OAuth tokens. Token verifier for Google OAuth tokens.
@ -46,7 +54,7 @@ by calling Google's tokeninfo API to check if they're valid and get user info.
**Methods:** **Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L88" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L111" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
verify_token(self, token: str) -> AccessToken | None verify_token(self, token: str) -> AccessToken | None
@ -55,10 +63,10 @@ verify_token(self, token: str) -> AccessToken | None
Verify Google OAuth token by calling Google's tokeninfo API. Verify Google OAuth token by calling Google's tokeninfo API.
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L185" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `GoogleDCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L208" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete Google OAuth provider for FastMCP. Complete Google OAuth DCR provider for FastMCP.
This provider makes it trivial to add Google OAuth protection to any This provider makes it trivial to add Google OAuth protection to any
FastMCP server. Just provide your Google OAuth app credentials and FastMCP server. Just provide your Google OAuth app credentials and
@ -70,3 +78,11 @@ Features:
- User information extraction from Google APIs - User information extraction from Google APIs
- Minimal configuration required - Minimal configuration required
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L339" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use GoogleDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.

View file

@ -10,7 +10,7 @@ WorkOS authentication providers for FastMCP.
This module provides two WorkOS authentication strategies: 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 2. AuthKitProvider - DCR-compliant provider for WorkOS AuthKit
Choose based on your WorkOS setup and authentication requirements. Choose based on your WorkOS setup and authentication requirements.
@ -18,13 +18,21 @@ Choose based on your WorkOS setup and authentication requirements.
## Classes ## Classes
### `WorkOSProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `WorkOSDCRProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for WorkOS OAuth provider. Settings for WorkOS OAuth DCR provider.
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> **Methods:**
#### `settings_customise_sources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
settings_customise_sources(cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)
```
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for WorkOS OAuth tokens. Token verifier for WorkOS OAuth tokens.
@ -35,7 +43,7 @@ the /oauth2/userinfo endpoint to check validity and get user info.
**Methods:** **Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L81" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
verify_token(self, token: str) -> AccessToken | None verify_token(self, token: str) -> AccessToken | None
@ -44,16 +52,16 @@ verify_token(self, token: str) -> AccessToken | None
Verify WorkOS OAuth token by calling userinfo endpoint. Verify WorkOS OAuth token by calling userinfo endpoint.
### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L128" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `WorkOSDCRProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete WorkOS OAuth provider for FastMCP. 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. It provides OAuth2 authentication for users through WorkOS Connect applications.
Features: Features:
- Transparent OAuth proxy to WorkOS AuthKit - Transparent OAuth DCR proxy to WorkOS AuthKit
- Automatic token validation via userinfo endpoint - Automatic token validation via userinfo endpoint
- User information extraction from ID tokens - User information extraction from ID tokens
- Support for standard OAuth scopes (openid, profile, email) - Support for standard OAuth scopes (openid, profile, email)
@ -65,9 +73,17 @@ Setup Requirements:
4. Note your Client ID and Client Secret 4. Note your Client ID and Client Secret
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L297" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Deprecated: Use WorkOSDCRProvider instead.
This alias is provided for backwards compatibility and will be removed in a future version.
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
AuthKit metadata provider for DCR (Dynamic Client Registration). AuthKit metadata provider for DCR (Dynamic Client Registration).
@ -93,7 +109,7 @@ https://workos.com/docs/authkit/mcp/integrating/token-verification
**Methods:** **Methods:**
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L413" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
get_routes(self, mcp_path: str | None = None) -> list[Route] get_routes(self, mcp_path: str | None = None) -> list[Route]

View file

@ -7,13 +7,13 @@ sidebarTitle: http
## Functions ## Functions
### `set_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L75" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `set_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L77" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
set_http_request(request: Request) -> Generator[Request, None, None] set_http_request(request: Request) -> Generator[Request, None, None]
``` ```
### `create_base_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L99" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `create_base_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L101" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
create_base_app(routes: list[BaseRoute], middleware: list[Middleware], debug: bool = False, lifespan: Callable | None = None) -> StarletteWithLifespan create_base_app(routes: list[BaseRoute], middleware: list[Middleware], debug: bool = False, lifespan: Callable | None = None) -> StarletteWithLifespan
@ -32,7 +32,7 @@ Create a base Starlette app with common middleware and routes.
- A Starlette application - A Starlette application
### `create_sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `create_sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L129" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
create_sse_app(server: FastMCP[LifespanResultT], message_path: str, sse_path: str, auth: AuthProvider | None = None, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan create_sse_app(server: FastMCP[LifespanResultT], message_path: str, sse_path: str, auth: AuthProvider | None = None, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
@ -54,7 +54,7 @@ Returns:
A Starlette application with RequestContextMiddleware A Starlette application with RequestContextMiddleware
### `create_streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L253" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `create_streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
create_streamable_http_app(server: FastMCP[LifespanResultT], streamable_http_path: str, event_store: EventStore | None = None, auth: AuthProvider | None = None, json_response: bool = False, stateless_http: bool = False, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan create_streamable_http_app(server: FastMCP[LifespanResultT], streamable_http_path: str, event_store: EventStore | None = None, auth: AuthProvider | None = None, json_response: bool = False, stateless_http: bool = False, debug: bool = False, routes: list[BaseRoute] | None = None, middleware: list[Middleware] | None = None) -> StarletteWithLifespan
@ -80,23 +80,23 @@ Return an instance of the StreamableHTTP server app.
## Classes ## Classes
### `StreamableHTTPASGIApp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `StreamableHTTPASGIApp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
ASGI application wrapper for Streamable HTTP server transport. ASGI application wrapper for Streamable HTTP server transport.
### `StarletteWithLifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `StarletteWithLifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
**Methods:** **Methods:**
#### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> #### `lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python ```python
lifespan(self) -> Lifespan[Starlette] lifespan(self) -> Lifespan[Starlette]
``` ```
### `RequestContextMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L83" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup> ### `RequestContextMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/http.py#L85" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Middleware that stores each request in a ContextVar Middleware that stores each request in a ContextVar

View file

@ -132,13 +132,13 @@ When identity providers require manual app registration and fixed credentials, `
This class solves the fundamental incompatibility between MCP's expectation of dynamic registration and traditional OAuth providers' requirement for manual app registration. This class solves the fundamental incompatibility between MCP's expectation of dynamic registration and traditional OAuth providers' requirement for manual app registration.
For example, the built-in `GitHubProvider` extends `OAuthProxy` to work with GitHub's OAuth system: For example, the built-in `GitHubDCRProvider` extends `OAuthProxy` to work with GitHub's OAuth system:
```python ```python
from fastmcp import FastMCP 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...", # Your GitHub OAuth App ID client_id="Ov23li...", # Your GitHub OAuth App ID
client_secret="abc123...", # Your GitHub OAuth App Secret client_secret="abc123...", # Your GitHub OAuth App Secret
base_url="https://your-server.com" base_url="https://your-server.com"
@ -202,10 +202,10 @@ Authentication providers are configured by specifying the full module path to th
<ParamField path="FASTMCP_SERVER_AUTH" type="string"> <ParamField path="FASTMCP_SERVER_AUTH" type="string">
The full module path to the authentication provider class. Examples: The full module path to the authentication provider class. Examples:
- `fastmcp.server.auth.providers.github.GitHubProvider` - GitHub OAuth - `fastmcp.server.auth.providers.github.GitHubDCRProvider` - GitHub OAuth
- `fastmcp.server.auth.providers.google.GoogleProvider` - Google OAuth - `fastmcp.server.auth.providers.google.GoogleDCRProvider` - Google OAuth
- `fastmcp.server.auth.providers.jwt.JWTVerifier` - JWT token verification - `fastmcp.server.auth.providers.jwt.JWTVerifier` - JWT token verification
- `fastmcp.server.auth.providers.workos.WorkOSProvider` - WorkOS OAuth - `fastmcp.server.auth.providers.workos.WorkOSDCRProvider` - WorkOS OAuth
- `fastmcp.server.auth.providers.workos.AuthKitProvider` - WorkOS AuthKit - `fastmcp.server.auth.providers.workos.AuthKitProvider` - WorkOS AuthKit
- `mycompany.auth.CustomProvider` - Your custom provider class - `mycompany.auth.CustomProvider` - Your custom provider class
</ParamField> </ParamField>
@ -214,14 +214,14 @@ When using providers like GitHub or Google, you'll need to set provider-specific
```bash ```bash
# GitHub OAuth # GitHub OAuth
export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubDCRProvider
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..." export FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID="Ov23li..."
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="github_pat_..." export FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET="github_pat_..."
# Google OAuth # Google OAuth
export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleProvider export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.google.GoogleDCRProvider
export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID="123456.apps.googleusercontent.com" export FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_ID="123456.apps.googleusercontent.com"
export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET="GOCSPX-..." export FASTMCP_SERVER_AUTH_GOOGLE_DCR_CLIENT_SECRET="GOCSPX-..."
``` ```
#### Provider-Specific Configuration #### Provider-Specific Configuration

View file

@ -131,7 +131,7 @@ mcp = FastMCP(name="My Server", auth=auth)
**Example with mounting:** **Example with mounting:**
```python ```python
auth = GitHubProvider( auth = GitHubDCRProvider(
base_url="http://localhost:8000/api", # OAuth endpoints under /api base_url="http://localhost:8000/api", # OAuth endpoints under /api
issuer_url="http://localhost:8000" # Auth server metadata at root issuer_url="http://localhost:8000" # Auth server metadata at root
) )
@ -289,9 +289,9 @@ auth = OAuthProxy(
FastMCP includes pre-configured providers for common services: FastMCP includes pre-configured providers for common services:
```python ```python
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-github-app-id", client_id="your-github-app-id",
client_secret="your-github-app-secret", client_secret="your-github-app-secret",
base_url="https://your-server.com" base_url="https://your-server.com"
@ -300,7 +300,7 @@ auth = GitHubProvider(
mcp = FastMCP(name="My Server", auth=auth) mcp = FastMCP(name="My Server", auth=auth)
``` ```
Available providers include `GitHubProvider`, `GoogleProvider`, and others. These handle token verification automatically. Available providers include `GitHubDCRProvider`, `GoogleDCRProvider`, and others. These handle token verification automatically.
### Token Verification ### Token Verification
@ -524,12 +524,12 @@ For production deployments, configure the OAuth proxy through environment variab
```bash ```bash
# Specify the provider implementation # Specify the provider implementation
export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubProvider export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.github.GitHubDCRProvider
# Provider-specific credentials # Provider-specific credentials
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="Ov23li..." export FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_ID="Ov23li..."
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="abc123..." export FASTMCP_SERVER_AUTH_GITHUB_DCR_CLIENT_SECRET="abc123..."
export FASTMCP_SERVER_AUTH_GITHUB_BASE_URL="https://your-production-server.com" export FASTMCP_SERVER_AUTH_GITHUB_DCR_BASE_URL="https://your-production-server.com"
``` ```
With environment configuration, your server code simplifies to: With environment configuration, your server code simplifies to:

View file

@ -39,10 +39,10 @@ Here's how to implement the OIDC proxy with any provider:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.oidc_proxy import OIDCProxy from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
# Create the OIDC proxy # Create the OIDC proxy
auth = OIDCProxy( auth = OIDCDCRProxy(
# Provider's configuration URL # Provider's configuration URL
config_url="https://provider.com/.well-known/openid-configuration", config_url="https://provider.com/.well-known/openid-configuration",
@ -62,7 +62,7 @@ mcp = FastMCP(name="My Server", auth=auth)
### Configuration Parameters ### Configuration Parameters
<Card icon="code" title="OIDCProxy Parameters"> <Card icon="code" title="OIDCDCRProxy Parameters">
<ParamField body="config_url" type="str" required> <ParamField body="config_url" type="str" required>
URL of your OAuth provider's OIDC configuration URL of your OAuth provider's OIDC configuration
</ParamField> </ParamField>
@ -136,7 +136,7 @@ Set this if your provider requires a specific authentication method and the defa
from fastmcp.utilities.storage import InMemoryStorage from fastmcp.utilities.storage import InMemoryStorage
# Use in-memory storage for testing (clients lost on restart) # Use in-memory storage for testing (clients lost on restart)
auth = OIDCProxy(..., client_storage=InMemoryStorage()) auth = OIDCDCRProxy(..., client_storage=InMemoryStorage())
``` ```
</ParamField> </ParamField>
@ -147,9 +147,9 @@ auth = OIDCProxy(..., client_storage=InMemoryStorage())
FastMCP includes pre-configured OIDC providers for common services: FastMCP includes pre-configured OIDC providers for common services:
```python ```python
from fastmcp.server.auth.providers.auth0 import Auth0Provider from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
auth = Auth0Provider( auth = Auth0DCRProvider(
config_url="https://.../.well-known/openid-configuration", config_url="https://.../.well-known/openid-configuration",
client_id="your-auth0-client-id", client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret", client_secret="your-auth0-client-secret",
@ -160,7 +160,7 @@ auth = Auth0Provider(
mcp = FastMCP(name="My Server", auth=auth) mcp = FastMCP(name="My Server", auth=auth)
``` ```
Available providers include `Auth0Provider` at present. Available providers include `Auth0DCRProvider` at present.
### Scope Configuration ### Scope Configuration
@ -176,14 +176,14 @@ For production deployments, configure the OIDC proxy through environment variabl
```bash ```bash
# Specify the provider implementation # Specify the provider implementation
export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0Provider export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.auth0.Auth0DCRProvider
# Provider-specific credentials # Provider-specific credentials
export FASTMCP_SERVER_AUTH_AUTH0_CONFIG_URL=https://.../.well-known/openid-configuration export FASTMCP_SERVER_AUTH_AUTH0_DCR_CONFIG_URL=https://.../.well-known/openid-configuration
export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB export FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_ID=tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB
export FASTMCP_SERVER_AUTH_AUTH0_CLIENT_SECRET=vPYqbjemq... export FASTMCP_SERVER_AUTH_AUTH0_DCR_CLIENT_SECRET=vPYqbjemq...
export FASTMCP_SERVER_AUTH_AUTH0_AUDIENCE=https://... export FASTMCP_SERVER_AUTH_AUTH0_DCR_AUDIENCE=https://...
export FASTMCP_SERVER_AUTH_AUTH0_BASE_URL=https://localhost:8000 export FASTMCP_SERVER_AUTH_AUTH0_DCR_BASE_URL=https://localhost:8000
``` ```
With environment configuration, your server code simplifies to: With environment configuration, your server code simplifies to:

View file

@ -57,10 +57,10 @@ middleware = ResponseCachingMiddleware(
Or with OAuth token storage: Or with OAuth token storage:
```python ```python
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
from key_value.aio.stores.disk import DiskStore from key_value.aio.stores.disk import DiskStore
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-id", client_id="your-id",
client_secret="your-secret", client_secret="your-secret",
base_url="https://your-server.com", base_url="https://your-server.com",
@ -110,10 +110,10 @@ For OAuth token storage:
```python ```python
import os import os
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
from key_value.aio.stores.redis import RedisStore from key_value.aio.stores.redis import RedisStore
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id=os.environ["GITHUB_CLIENT_ID"], client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"], client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url="https://your-server.com", base_url="https://your-server.com",
@ -152,9 +152,9 @@ The [OAuth Proxy](/servers/auth/oauth-proxy) and OAuth auth providers use storag
```python ```python
# In-memory storage (default behavior - lost on restart) # In-memory storage (default behavior - lost on restart)
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-id", client_id="your-id",
client_secret="your-secret", client_secret="your-secret",
base_url="https://your-server.com" base_url="https://your-server.com"
@ -165,10 +165,10 @@ For production with token persistence across restarts, configure persistent stor
```python ```python
import os import os
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
from key_value.aio.stores.redis import RedisStore from key_value.aio.stores.redis import RedisStore
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id=os.environ["GITHUB_CLIENT_ID"], client_id=os.environ["GITHUB_CLIENT_ID"],
client_secret=os.environ["GITHUB_CLIENT_SECRET"], client_secret=os.environ["GITHUB_CLIENT_SECRET"],
base_url="https://your-server.com", base_url="https://your-server.com",

View file

@ -18,14 +18,14 @@ import os
from dotenv import load_dotenv from dotenv import load_dotenv
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.aws import AWSCognitoProvider from fastmcp.server.auth.providers.aws import AWSCognitoDCRProvider
from fastmcp.server.dependencies import get_access_token from fastmcp.server.dependencies import get_access_token
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
load_dotenv(".env", override=True) load_dotenv(".env", override=True)
auth = AWSCognitoProvider( auth = AWSCognitoDCRProvider(
user_pool_id=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID") or "", user_pool_id=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_USER_POOL_ID") or "",
aws_region=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION") aws_region=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_AWS_REGION")
or "eu-central-1", or "eu-central-1",

View file

@ -15,9 +15,9 @@ To run:
import os import os
from fastmcp import FastMCP 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=os.getenv("AZURE_CLIENT_ID") or "", client_id=os.getenv("AZURE_CLIENT_ID") or "",
client_secret=os.getenv("AZURE_CLIENT_SECRET") or "", client_secret=os.getenv("AZURE_CLIENT_SECRET") or "",
tenant_id=os.getenv("AZURE_TENANT_ID") tenant_id=os.getenv("AZURE_TENANT_ID")

View file

@ -13,9 +13,9 @@ To run:
import os import os
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubProvider as GitHubDCRProvider
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID") or "", client_id=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID") or "",
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET") or "", client_secret=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET") or "",
base_url="http://localhost:8000", base_url="http://localhost:8000",

View file

@ -13,9 +13,9 @@ To run:
import os import os
from fastmcp import FastMCP 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=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID") or "", client_id=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID") or "",
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET") or "", client_secret=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET") or "",
base_url="http://localhost:8000", base_url="http://localhost:8000",

View file

@ -14,9 +14,9 @@ To run:
import os import os
from fastmcp import FastMCP 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=os.getenv("WORKOS_CLIENT_ID") or "", client_id=os.getenv("WORKOS_CLIENT_ID") or "",
client_secret=os.getenv("WORKOS_CLIENT_SECRET") or "", client_secret=os.getenv("WORKOS_CLIENT_SECRET") or "",
authkit_domain=os.getenv("WORKOS_AUTHKIT_DOMAIN") or "https://your-app.authkit.app", authkit_domain=os.getenv("WORKOS_AUTHKIT_DOMAIN") or "https://your-app.authkit.app",

View file

@ -6,8 +6,10 @@ from .auth import (
AuthProvider, AuthProvider,
) )
from .providers.jwt import JWTVerifier, StaticTokenVerifier from .providers.jwt import JWTVerifier, StaticTokenVerifier
from .oauth_proxy import OAuthProxy from .oauth_dcr_proxy import OAuthDCRProxy
import warnings
import fastmcp
__all__ = [ __all__ = [
"AuthProvider", "AuthProvider",
@ -17,7 +19,7 @@ __all__ = [
"StaticTokenVerifier", "StaticTokenVerifier",
"RemoteAuthProvider", "RemoteAuthProvider",
"AccessToken", "AccessToken",
"OAuthProxy", "OAuthDCRProxy",
] ]
@ -27,4 +29,18 @@ def __getattr__(name: str):
from .providers.bearer import BearerAuthProvider from .providers.bearer import BearerAuthProvider
return 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}'") 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

@ -0,0 +1,350 @@
"""OIDC Proxy Provider for FastMCP.
This provider acts as a transparent proxy to an upstream OIDC compliant Authorization
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and
forwarding of all OAuth flows.
This implementation is based on:
OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
"""
from collections.abc import Sequence
import httpx
from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, BaseModel, model_validator
from typing_extensions import Self
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.logging import get_logger
logger = get_logger(__name__)
class OIDCConfiguration(BaseModel):
"""OIDC Configuration.
See:
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
https://datatracker.ietf.org/doc/html/rfc8414#section-2
"""
strict: bool = True
# OpenID Connect Discovery 1.0
issuer: AnyHttpUrl | str | None = None # Strict
authorization_endpoint: AnyHttpUrl | str | None = None # Strict
token_endpoint: AnyHttpUrl | str | None = None # Strict
userinfo_endpoint: AnyHttpUrl | str | None = None
jwks_uri: AnyHttpUrl | str | None = None # Strict
registration_endpoint: AnyHttpUrl | str | None = None
scopes_supported: Sequence[str] | None = None
response_types_supported: Sequence[str] | None = None # Strict
response_modes_supported: Sequence[str] | None = None
grant_types_supported: Sequence[str] | None = None
acr_values_supported: Sequence[str] | None = None
subject_types_supported: Sequence[str] | None = None # Strict
id_token_signing_alg_values_supported: Sequence[str] | None = None # Strict
id_token_encryption_alg_values_supported: Sequence[str] | None = None
id_token_encryption_enc_values_supported: Sequence[str] | None = None
userinfo_signing_alg_values_supported: Sequence[str] | None = None
userinfo_encryption_alg_values_supported: Sequence[str] | None = None
userinfo_encryption_enc_values_supported: Sequence[str] | None = None
request_object_signing_alg_values_supported: Sequence[str] | None = None
request_object_encryption_alg_values_supported: Sequence[str] | None = None
request_object_encryption_enc_values_supported: Sequence[str] | None = None
token_endpoint_auth_methods_supported: Sequence[str] | None = None
token_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
display_values_supported: Sequence[str] | None = None
claim_types_supported: Sequence[str] | None = None
claims_supported: Sequence[str] | None = None
service_documentation: AnyHttpUrl | str | None = None
claims_locales_supported: Sequence[str] | None = None
ui_locales_supported: Sequence[str] | None = None
claims_parameter_supported: bool | None = None
request_parameter_supported: bool | None = None
request_uri_parameter_supported: bool | None = None
require_request_uri_registration: bool | None = None
op_policy_uri: AnyHttpUrl | str | None = None
op_tos_uri: AnyHttpUrl | str | None = None
# OAuth 2.0 Authorization Server Metadata
revocation_endpoint: AnyHttpUrl | str | None = None
revocation_endpoint_auth_methods_supported: Sequence[str] | None = None
revocation_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
introspection_endpoint: AnyHttpUrl | str | None = None
introspection_endpoint_auth_methods_supported: Sequence[str] | None = None
introspection_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = (
None
)
code_challenge_methods_supported: Sequence[str] | None = None
signed_metadata: str | None = None
@model_validator(mode="after")
def _enforce_strict(self) -> Self:
"""Enforce strict rules."""
if not self.strict:
return self
def enforce(attr: str, is_url: bool = False) -> None:
value = getattr(self, attr, None)
if not value:
message = f"Missing required configuration metadata: {attr}"
logger.error(message)
raise ValueError(message)
if not is_url or isinstance(value, AnyHttpUrl):
return
try:
AnyHttpUrl(value)
except Exception:
message = f"Invalid URL for configuration metadata: {attr}"
logger.error(message)
raise ValueError(message)
enforce("issuer", True)
enforce("authorization_endpoint", True)
enforce("token_endpoint", True)
enforce("jwks_uri", True)
enforce("response_types_supported")
enforce("subject_types_supported")
enforce("id_token_signing_alg_values_supported")
return self
@classmethod
def get_oidc_configuration(
cls, config_url: AnyHttpUrl, *, strict: bool | None, timeout_seconds: int | None
) -> Self:
"""Get the OIDC configuration for the specified config URL.
Args:
config_url: The OIDC config URL
strict: The strict flag for the configuration
timeout_seconds: HTTP request timeout in seconds
"""
get_kwargs = {}
if timeout_seconds is not None:
get_kwargs["timeout"] = timeout_seconds
try:
response = httpx.get(str(config_url), **get_kwargs)
response.raise_for_status()
config_data = response.json()
if strict is not None:
config_data["strict"] = strict
return cls.model_validate(config_data)
except Exception:
logger.exception(
f"Unable to get OIDC configuration for config url: {config_url}"
)
raise
class OIDCDCRProxy(OAuthDCRProxy):
"""OAuth provider that wraps OAuthDCRProxy to provide configuration via an OIDC configuration URL.
This provider makes it easier to add OAuth protection for any upstream provider
that is OIDC compliant.
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
# Simple OIDC based protection
auth = OIDCDCRProxy(
config_url="https://oidc.config.url",
client_id="your-oidc-client-id",
client_secret="your-oidc-client-secret",
base_url="https://your.server.url",
)
mcp = FastMCP("My Protected Server", auth=auth)
```
"""
oidc_config: OIDCConfiguration
def __init__(
self,
*,
# OIDC configuration
config_url: AnyHttpUrl | str,
strict: bool | None = None,
# Upstream server configuration
client_id: str,
client_secret: str,
audience: str | None = None,
timeout_seconds: int | None = None,
# Token verifier
algorithm: str | None = None,
required_scopes: list[str] | None = None,
# FastMCP server configuration
base_url: AnyHttpUrl | str,
issuer_url: AnyHttpUrl | str | None = None,
redirect_path: str | None = None,
# Client configuration
allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
# Token validation configuration
token_endpoint_auth_method: str | None = None,
) -> None:
"""Initialize the OIDC proxy provider.
Args:
config_url: URL of upstream configuration
strict: Optional strict flag for the configuration
client_id: Client ID registered with upstream server
client_secret: Client secret for upstream server
audience: Audience for upstream server
timeout_seconds: HTTP request timeout in seconds
algorithm: Token verifier algorithm
required_scopes: Required OAuth scopes
base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If None (default), only localhost redirect URIs are allowed.
If empty list, all redirect URIs are allowed (not recommended for production).
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
token_endpoint_auth_method: Token endpoint authentication method for upstream server.
Common values: "client_secret_basic", "client_secret_post", "none".
If None, authlib will use its default (typically "client_secret_basic").
"""
if not config_url:
raise ValueError("Missing required config URL")
if not client_id:
raise ValueError("Missing required client id")
if not client_secret:
raise ValueError("Missing required client secret")
if not base_url:
raise ValueError("Missing required base URL")
if isinstance(config_url, str):
config_url = AnyHttpUrl(config_url)
self.oidc_config = self.get_oidc_configuration(
config_url, strict, timeout_seconds
)
if (
not self.oidc_config.authorization_endpoint
or not self.oidc_config.token_endpoint
):
logger.debug(f"Invalid OIDC Configuration: {self.oidc_config}")
raise ValueError("Missing required OIDC endpoints")
revocation_endpoint = (
str(self.oidc_config.revocation_endpoint)
if self.oidc_config.revocation_endpoint
else None
)
token_verifier = self.get_token_verifier(
algorithm=algorithm,
audience=audience,
required_scopes=required_scopes,
timeout_seconds=timeout_seconds,
)
init_kwargs = {
"upstream_authorization_endpoint": str(
self.oidc_config.authorization_endpoint
),
"upstream_token_endpoint": str(self.oidc_config.token_endpoint),
"upstream_client_id": client_id,
"upstream_client_secret": client_secret,
"upstream_revocation_endpoint": revocation_endpoint,
"token_verifier": token_verifier,
"base_url": base_url,
"issuer_url": issuer_url or base_url,
"service_documentation_url": self.oidc_config.service_documentation,
"allowed_client_redirect_uris": allowed_client_redirect_uris,
"client_storage": client_storage,
"token_endpoint_auth_method": token_endpoint_auth_method,
}
if redirect_path:
init_kwargs["redirect_path"] = redirect_path
if audience:
extra_params = {"audience": audience}
init_kwargs["extra_authorize_params"] = extra_params
init_kwargs["extra_token_params"] = extra_params
super().__init__(**init_kwargs)
def get_oidc_configuration(
self,
config_url: AnyHttpUrl,
strict: bool | None,
timeout_seconds: int | None,
) -> OIDCConfiguration:
"""Gets the OIDC configuration for the specified configuration URL.
Args:
config_url: The OIDC configuration URL
strict: The strict flag for the configuration
timeout_seconds: HTTP request timeout in seconds
"""
return OIDCConfiguration.get_oidc_configuration(
config_url, strict=strict, timeout_seconds=timeout_seconds
)
def get_token_verifier(
self,
*,
algorithm: str | None = None,
audience: str | None = None,
required_scopes: list[str] | None = None,
timeout_seconds: int | None = None,
) -> TokenVerifier:
"""Creates the token verifier for the specified OIDC configuration and arguments.
Args:
algorithm: Optional token verifier algorithm
audience: Optional token verifier audience
required_scopes: Optional token verifier required_scopes
timeout_seconds: HTTP request timeout in seconds
"""
return JWTVerifier(
jwks_uri=str(self.oidc_config.jwks_uri),
issuer=str(self.oidc_config.issuer),
algorithm=algorithm,
audience=audience,
required_scopes=required_scopes,
)

View file

@ -1,350 +1,24 @@
"""OIDC Proxy Provider for FastMCP. """Backwards compatibility shim for oidc_proxy.py
This provider acts as a transparent proxy to an upstream OIDC compliant Authorization The OIDCProxy class has been moved to fastmcp.server.auth.oidc_dcr_proxy.OIDCDCRProxy
Server. It leverages the OAuthProxy class to handle Dynamic Client Registration and for better organization. This module provides a backwards-compatible import.
forwarding of all OAuth flows.
This implementation is based on:
OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html
OAuth 2.0 Authorization Server Metadata - https://datatracker.ietf.org/doc/html/rfc8414
""" """
from collections.abc import Sequence import warnings
import httpx import fastmcp
from key_value.aio.protocols import AsyncKeyValue from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy as OIDCProxy
from pydantic import AnyHttpUrl, BaseModel, model_validator
from typing_extensions import Self
from fastmcp.server.auth import TokenVerifier # Re-export for backwards compatibility
from fastmcp.server.auth.oauth_proxy import OAuthProxy __all__ = ["OIDCProxy"]
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.logging import get_logger
logger = get_logger(__name__) # Deprecated in 2.13
if fastmcp.settings.deprecation_warnings:
warnings.warn(
class OIDCConfiguration(BaseModel): "The `fastmcp.server.auth.oidc_proxy` module is deprecated "
"""OIDC Configuration. "and will be removed in a future version. "
"Please use `fastmcp.server.auth.oidc_dcr_proxy.OIDCDCRProxy` "
See: "instead of this module's OIDCProxy.",
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata DeprecationWarning,
https://datatracker.ietf.org/doc/html/rfc8414#section-2 stacklevel=2,
"""
strict: bool = True
# OpenID Connect Discovery 1.0
issuer: AnyHttpUrl | str | None = None # Strict
authorization_endpoint: AnyHttpUrl | str | None = None # Strict
token_endpoint: AnyHttpUrl | str | None = None # Strict
userinfo_endpoint: AnyHttpUrl | str | None = None
jwks_uri: AnyHttpUrl | str | None = None # Strict
registration_endpoint: AnyHttpUrl | str | None = None
scopes_supported: Sequence[str] | None = None
response_types_supported: Sequence[str] | None = None # Strict
response_modes_supported: Sequence[str] | None = None
grant_types_supported: Sequence[str] | None = None
acr_values_supported: Sequence[str] | None = None
subject_types_supported: Sequence[str] | None = None # Strict
id_token_signing_alg_values_supported: Sequence[str] | None = None # Strict
id_token_encryption_alg_values_supported: Sequence[str] | None = None
id_token_encryption_enc_values_supported: Sequence[str] | None = None
userinfo_signing_alg_values_supported: Sequence[str] | None = None
userinfo_encryption_alg_values_supported: Sequence[str] | None = None
userinfo_encryption_enc_values_supported: Sequence[str] | None = None
request_object_signing_alg_values_supported: Sequence[str] | None = None
request_object_encryption_alg_values_supported: Sequence[str] | None = None
request_object_encryption_enc_values_supported: Sequence[str] | None = None
token_endpoint_auth_methods_supported: Sequence[str] | None = None
token_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
display_values_supported: Sequence[str] | None = None
claim_types_supported: Sequence[str] | None = None
claims_supported: Sequence[str] | None = None
service_documentation: AnyHttpUrl | str | None = None
claims_locales_supported: Sequence[str] | None = None
ui_locales_supported: Sequence[str] | None = None
claims_parameter_supported: bool | None = None
request_parameter_supported: bool | None = None
request_uri_parameter_supported: bool | None = None
require_request_uri_registration: bool | None = None
op_policy_uri: AnyHttpUrl | str | None = None
op_tos_uri: AnyHttpUrl | str | None = None
# OAuth 2.0 Authorization Server Metadata
revocation_endpoint: AnyHttpUrl | str | None = None
revocation_endpoint_auth_methods_supported: Sequence[str] | None = None
revocation_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = None
introspection_endpoint: AnyHttpUrl | str | None = None
introspection_endpoint_auth_methods_supported: Sequence[str] | None = None
introspection_endpoint_auth_signing_alg_values_supported: Sequence[str] | None = (
None
) )
code_challenge_methods_supported: Sequence[str] | None = None
signed_metadata: str | None = None
@model_validator(mode="after")
def _enforce_strict(self) -> Self:
"""Enforce strict rules."""
if not self.strict:
return self
def enforce(attr: str, is_url: bool = False) -> None:
value = getattr(self, attr, None)
if not value:
message = f"Missing required configuration metadata: {attr}"
logger.error(message)
raise ValueError(message)
if not is_url or isinstance(value, AnyHttpUrl):
return
try:
AnyHttpUrl(value)
except Exception:
message = f"Invalid URL for configuration metadata: {attr}"
logger.error(message)
raise ValueError(message)
enforce("issuer", True)
enforce("authorization_endpoint", True)
enforce("token_endpoint", True)
enforce("jwks_uri", True)
enforce("response_types_supported")
enforce("subject_types_supported")
enforce("id_token_signing_alg_values_supported")
return self
@classmethod
def get_oidc_configuration(
cls, config_url: AnyHttpUrl, *, strict: bool | None, timeout_seconds: int | None
) -> Self:
"""Get the OIDC configuration for the specified config URL.
Args:
config_url: The OIDC config URL
strict: The strict flag for the configuration
timeout_seconds: HTTP request timeout in seconds
"""
get_kwargs = {}
if timeout_seconds is not None:
get_kwargs["timeout"] = timeout_seconds
try:
response = httpx.get(str(config_url), **get_kwargs)
response.raise_for_status()
config_data = response.json()
if strict is not None:
config_data["strict"] = strict
return cls.model_validate(config_data)
except Exception:
logger.exception(
f"Unable to get OIDC configuration for config url: {config_url}"
)
raise
class OIDCProxy(OAuthProxy):
"""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
that is OIDC compliant.
Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.oidc_proxy import OIDCProxy
# Simple OIDC based protection
auth = OIDCProxy(
config_url="https://oidc.config.url",
client_id="your-oidc-client-id",
client_secret="your-oidc-client-secret",
base_url="https://your.server.url",
)
mcp = FastMCP("My Protected Server", auth=auth)
```
"""
oidc_config: OIDCConfiguration
def __init__(
self,
*,
# OIDC configuration
config_url: AnyHttpUrl | str,
strict: bool | None = None,
# Upstream server configuration
client_id: str,
client_secret: str,
audience: str | None = None,
timeout_seconds: int | None = None,
# Token verifier
algorithm: str | None = None,
required_scopes: list[str] | None = None,
# FastMCP server configuration
base_url: AnyHttpUrl | str,
issuer_url: AnyHttpUrl | str | None = None,
redirect_path: str | None = None,
# Client configuration
allowed_client_redirect_uris: list[str] | None = None,
client_storage: AsyncKeyValue | None = None,
# Token validation configuration
token_endpoint_auth_method: str | None = None,
) -> None:
"""Initialize the OIDC proxy provider.
Args:
config_url: URL of upstream configuration
strict: Optional strict flag for the configuration
client_id: Client ID registered with upstream server
client_secret: Client secret for upstream server
audience: Audience for upstream server
timeout_seconds: HTTP request timeout in seconds
algorithm: Token verifier algorithm
required_scopes: Required OAuth scopes
base_url: Public URL where OAuth endpoints will be accessible (includes any mount path)
issuer_url: Issuer URL for OAuth metadata (defaults to base_url). Use root-level URL
to avoid 404s during discovery when mounting under a path.
redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If None (default), only localhost redirect URIs are allowed.
If empty list, all redirect URIs are allowed (not recommended for production).
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided
token_endpoint_auth_method: Token endpoint authentication method for upstream server.
Common values: "client_secret_basic", "client_secret_post", "none".
If None, authlib will use its default (typically "client_secret_basic").
"""
if not config_url:
raise ValueError("Missing required config URL")
if not client_id:
raise ValueError("Missing required client id")
if not client_secret:
raise ValueError("Missing required client secret")
if not base_url:
raise ValueError("Missing required base URL")
if isinstance(config_url, str):
config_url = AnyHttpUrl(config_url)
self.oidc_config = self.get_oidc_configuration(
config_url, strict, timeout_seconds
)
if (
not self.oidc_config.authorization_endpoint
or not self.oidc_config.token_endpoint
):
logger.debug(f"Invalid OIDC Configuration: {self.oidc_config}")
raise ValueError("Missing required OIDC endpoints")
revocation_endpoint = (
str(self.oidc_config.revocation_endpoint)
if self.oidc_config.revocation_endpoint
else None
)
token_verifier = self.get_token_verifier(
algorithm=algorithm,
audience=audience,
required_scopes=required_scopes,
timeout_seconds=timeout_seconds,
)
init_kwargs = {
"upstream_authorization_endpoint": str(
self.oidc_config.authorization_endpoint
),
"upstream_token_endpoint": str(self.oidc_config.token_endpoint),
"upstream_client_id": client_id,
"upstream_client_secret": client_secret,
"upstream_revocation_endpoint": revocation_endpoint,
"token_verifier": token_verifier,
"base_url": base_url,
"issuer_url": issuer_url or base_url,
"service_documentation_url": self.oidc_config.service_documentation,
"allowed_client_redirect_uris": allowed_client_redirect_uris,
"client_storage": client_storage,
"token_endpoint_auth_method": token_endpoint_auth_method,
}
if redirect_path:
init_kwargs["redirect_path"] = redirect_path
if audience:
extra_params = {"audience": audience}
init_kwargs["extra_authorize_params"] = extra_params
init_kwargs["extra_token_params"] = extra_params
super().__init__(**init_kwargs)
def get_oidc_configuration(
self,
config_url: AnyHttpUrl,
strict: bool | None,
timeout_seconds: int | None,
) -> OIDCConfiguration:
"""Gets the OIDC configuration for the specified configuration URL.
Args:
config_url: The OIDC configuration URL
strict: The strict flag for the configuration
timeout_seconds: HTTP request timeout in seconds
"""
return OIDCConfiguration.get_oidc_configuration(
config_url, strict=strict, timeout_seconds=timeout_seconds
)
def get_token_verifier(
self,
*,
algorithm: str | None = None,
audience: str | None = None,
required_scopes: list[str] | None = None,
timeout_seconds: int | None = None,
) -> TokenVerifier:
"""Creates the token verifier for the specified OIDC configuration and arguments.
Args:
algorithm: Optional token verifier algorithm
audience: Optional token verifier audience
required_scopes: Optional token verifier required_scopes
timeout_seconds: HTTP request timeout in seconds
"""
return JWTVerifier(
jwks_uri=str(self.oidc_config.jwks_uri),
issuer=str(self.oidc_config.issuer),
algorithm=algorithm,
audience=audience,
required_scopes=required_scopes,
)

View file

@ -6,10 +6,10 @@ just the configuration URL, client ID, client secret, audience, and base URL.
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# Simple Auth0 OAuth protection # Simple Auth0 OAuth protection
auth = Auth0Provider( auth = Auth0DCRProvider(
config_url="https://auth0.config.url", config_url="https://auth0.config.url",
client_id="your-auth0-client-id", client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret", client_secret="your-auth0-client-secret",
@ -21,12 +21,19 @@ Example:
``` ```
""" """
import warnings
from key_value.aio.protocols import AsyncKeyValue from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings
from fastmcp.server.auth.oidc_proxy import OIDCProxy import fastmcp.settings
from fastmcp.settings import ENV_FILE from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
)
from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT from fastmcp.utilities.types import NotSet, NotSetT
@ -34,15 +41,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__) logger = get_logger(__name__)
class Auth0ProviderSettings(BaseSettings): class Auth0DCRProviderSettings(BaseSettings):
"""Settings for Auth0 OIDC provider.""" """Settings for Auth0 OIDC DCR provider."""
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AUTH0_", env_prefix="FASTMCP_SERVER_AUTH_AUTH0_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_AUTH0_DCR_", "FASTMCP_SERVER_AUTH_AUTH0_"],
env_file=ENV_FILE, env_file=ENV_FILE,
extra="ignore", 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 config_url: AnyHttpUrl | None = None
client_id: str | None = None client_id: str | None = None
client_secret: SecretStr | None = None client_secret: SecretStr | None = None
@ -59,8 +83,8 @@ class Auth0ProviderSettings(BaseSettings):
return parse_scopes(v) return parse_scopes(v)
class Auth0Provider(OIDCProxy): class Auth0DCRProvider(OIDCDCRProxy):
"""An Auth0 provider implementation for FastMCP. """An Auth0 DCR provider implementation for FastMCP.
This provider is a complete Auth0 integration that's ready to use with 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. just the configuration URL, client ID, client secret, audience, and base URL.
@ -68,10 +92,10 @@ class Auth0Provider(OIDCProxy):
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.auth0 import Auth0Provider from fastmcp.server.auth.providers.auth0 import Auth0DCRProvider
# Simple Auth0 OAuth protection # Simple Auth0 OAuth protection
auth = Auth0Provider( auth = Auth0DCRProvider(
config_url="https://auth0.config.url", config_url="https://auth0.config.url",
client_id="your-auth0-client-id", client_id="your-auth0-client-id",
client_secret="your-auth0-client-secret", client_secret="your-auth0-client-secret",
@ -113,7 +137,7 @@ class Auth0Provider(OIDCProxy):
If None (default), all URIs are allowed. If empty list, no URIs are allowed. 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 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 k: v
for k, v in { for k, v in {
@ -131,50 +155,67 @@ class Auth0Provider(OIDCProxy):
} }
) )
if not settings.config_url: if not provider_settings.config_url:
raise ValueError( 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( 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( 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( 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( 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 = { init_kwargs = {
"config_url": settings.config_url, "config_url": provider_settings.config_url,
"client_id": settings.client_id, "client_id": provider_settings.client_id,
"client_secret": settings.client_secret.get_secret_value(), "client_secret": provider_settings.client_secret.get_secret_value(),
"audience": settings.audience, "audience": provider_settings.audience,
"base_url": settings.base_url, "base_url": provider_settings.base_url,
"issuer_url": settings.issuer_url, "issuer_url": provider_settings.issuer_url,
"redirect_path": settings.redirect_path, "redirect_path": provider_settings.redirect_path,
"required_scopes": auth0_required_scopes, "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, "client_storage": client_storage,
} }
super().__init__(**init_kwargs) super().__init__(**init_kwargs)
logger.info( logger.info(
"Initialized Auth0 OAuth provider for client %s with scopes: %s", "Initialized Auth0 OAuth DCR provider for client %s with scopes: %s",
settings.client_id, provider_settings.client_id,
auth0_required_scopes, 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 fastmcp.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 from __future__ import annotations
import warnings
from key_value.aio.protocols import AsyncKeyValue from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings
import fastmcp.settings
from fastmcp.server.auth import TokenVerifier from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oidc_proxy import OIDCProxy from fastmcp.server.auth.oidc_dcr_proxy import OIDCDCRProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.settings import ENV_FILE from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
)
from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT from fastmcp.utilities.types import NotSet, NotSetT
@ -39,15 +46,35 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__) logger = get_logger(__name__)
class AWSCognitoProviderSettings(BaseSettings): class AWSCognitoDCRProviderSettings(BaseSettings):
"""Settings for AWS Cognito OAuth provider.""" """Settings for AWS Cognito OAuth DCR provider."""
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AWS_COGNITO_", 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, env_file=ENV_FILE,
extra="ignore", 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 user_pool_id: str | None = None
aws_region: str | None = None aws_region: str | None = None
client_id: str | None = None client_id: str | None = None
@ -91,8 +118,8 @@ class AWSCognitoTokenVerifier(JWTVerifier):
) )
class AWSCognitoProvider(OIDCProxy): class AWSCognitoDCRProvider(OIDCDCRProxy):
"""Complete AWS Cognito OAuth provider for FastMCP. """Complete AWS Cognito OAuth DCR provider for FastMCP.
This provider makes it trivial to add AWS Cognito OAuth protection to any 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, FastMCP server using OIDC Discovery. Just provide your Cognito User Pool details,
@ -107,9 +134,9 @@ class AWSCognitoProvider(OIDCProxy):
Example: Example:
```python ```python
from fastmcp import FastMCP 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", user_pool_id="eu-central-1_XXXXXXXXX",
aws_region="eu-central-1", aws_region="eu-central-1",
client_id="your-cognito-client-id", client_id="your-cognito-client-id",
@ -153,7 +180,7 @@ class AWSCognitoProvider(OIDCProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided 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 k: v
for k, v in { for k, v in {
@ -172,57 +199,78 @@ class AWSCognitoProvider(OIDCProxy):
) )
# Validate required settings # Validate required settings
if not settings.user_pool_id: if not provider_settings.user_pool_id:
raise ValueError( 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( 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( 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 # Apply defaults
required_scopes_final = settings.required_scopes or ["openid"] required_scopes_final = provider_settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris allowed_client_redirect_uris_final = (
aws_region_final = settings.aws_region or "eu-central-1" provider_settings.allowed_client_redirect_uris
redirect_path_final = settings.redirect_path or "/auth/callback" )
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 # 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 # Extract secret string from SecretStr
client_secret_str = ( 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 # 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 self.aws_region = aws_region_final
# Initialize OIDC proxy with Cognito discovery # Initialize OIDC proxy with Cognito discovery
super().__init__( super().__init__(
config_url=config_url, config_url=config_url,
client_id=settings.client_id, client_id=provider_settings.client_id,
client_secret=client_secret_str, client_secret=client_secret_str,
algorithm="RS256", algorithm="RS256",
required_scopes=required_scopes_final, required_scopes=required_scopes_final,
base_url=settings.base_url, base_url=provider_settings.base_url,
issuer_url=settings.issuer_url, issuer_url=provider_settings.issuer_url,
redirect_path=redirect_path_final, redirect_path=redirect_path_final,
allowed_client_redirect_uris=allowed_client_redirect_uris_final, allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage, client_storage=client_storage,
) )
logger.info( logger.info(
"Initialized AWS Cognito OAuth provider for client %s with scopes: %s", "Initialized AWS Cognito OAuth DCR provider for client %s with scopes: %s",
settings.client_id, provider_settings.client_id,
required_scopes_final, 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 fastmcp.settings.deprecation_warnings:
warnings.warn(
"AWSCognitoProvider is deprecated, use AWSCognitoDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)
def get_token_verifier( def get_token_verifier(
self, self,
*, *,

View file

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

View file

@ -7,10 +7,10 @@ GitHub's OAuth flow, token validation, and user management.
Example: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.github import GitHubProvider from fastmcp.server.auth.providers.github import GitHubDCRProvider
# Simple GitHub OAuth protection # Simple GitHub OAuth protection
auth = GitHubProvider( auth = GitHubDCRProvider(
client_id="your-github-client-id", client_id="your-github-client-id",
client_secret="your-github-client-secret" client_secret="your-github-client-secret"
) )
@ -21,15 +21,22 @@ Example:
from __future__ import annotations from __future__ import annotations
import warnings
import httpx import httpx
from key_value.aio.protocols import AsyncKeyValue from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings
import fastmcp.settings
from fastmcp.server.auth import TokenVerifier from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken 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.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
)
from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT from fastmcp.utilities.types import NotSet, NotSetT
@ -37,15 +44,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__) logger = get_logger(__name__)
class GitHubProviderSettings(BaseSettings): class GitHubDCRProviderSettings(BaseSettings):
"""Settings for GitHub OAuth provider.""" """Settings for GitHub OAuth DCR provider."""
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GITHUB_", env_prefix="FASTMCP_SERVER_AUTH_GITHUB_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_GITHUB_DCR_", "FASTMCP_SERVER_AUTH_GITHUB_"],
env_file=ENV_FILE, env_file=ENV_FILE,
extra="ignore", 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_id: str | None = None
client_secret: SecretStr | None = None client_secret: SecretStr | None = None
base_url: AnyHttpUrl | str | None = None base_url: AnyHttpUrl | str | None = None
@ -166,8 +190,8 @@ class GitHubTokenVerifier(TokenVerifier):
return None return None
class GitHubProvider(OAuthProxy): class GitHubDCRProvider(OAuthDCRProxy):
"""Complete GitHub OAuth provider for FastMCP. """Complete GitHub OAuth DCR provider for FastMCP.
This provider makes it trivial to add GitHub OAuth protection to any This provider makes it trivial to add GitHub OAuth protection to any
FastMCP server. Just provide your GitHub OAuth app credentials and FastMCP server. Just provide your GitHub OAuth app credentials and
@ -182,9 +206,9 @@ class GitHubProvider(OAuthProxy):
Example: Example:
```python ```python
from fastmcp import FastMCP 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_id="Ov23li...",
client_secret="abc123...", client_secret="abc123...",
base_url="https://my-server.com" base_url="https://my-server.com"
@ -223,7 +247,7 @@ class GitHubProvider(OAuthProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided 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 k: v
for k, v in { for k, v in {
@ -241,20 +265,21 @@ class GitHubProvider(OAuthProxy):
) )
# Validate required settings # Validate required settings
if not settings.client_id: if not provider_settings.client_id:
raise ValueError( 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( 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 # Apply defaults
timeout_seconds_final = provider_settings.timeout_seconds or 10
timeout_seconds_final = settings.timeout_seconds or 10 required_scopes_final = provider_settings.required_scopes or ["user"]
required_scopes_final = settings.required_scopes or ["user"] allowed_client_redirect_uris_final = (
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris provider_settings.allowed_client_redirect_uris
)
# Create GitHub token verifier # Create GitHub token verifier
token_verifier = GitHubTokenVerifier( token_verifier = GitHubTokenVerifier(
@ -264,26 +289,45 @@ class GitHubProvider(OAuthProxy):
# Extract secret string from SecretStr # Extract secret string from SecretStr
client_secret_str = ( 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 # Initialize OAuth proxy with GitHub endpoints
super().__init__( super().__init__(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize", upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token", 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, upstream_client_secret=client_secret_str,
token_verifier=token_verifier, token_verifier=token_verifier,
base_url=settings.base_url, base_url=provider_settings.base_url,
redirect_path=settings.redirect_path, redirect_path=provider_settings.redirect_path,
issuer_url=settings.issuer_url issuer_url=provider_settings.issuer_url
or settings.base_url, # Default to base_url if not specified or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final, allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage, client_storage=client_storage,
) )
logger.info( logger.info(
"Initialized GitHub OAuth provider for client %s with scopes: %s", "Initialized GitHub OAuth DCR provider for client %s with scopes: %s",
settings.client_id, provider_settings.client_id,
required_scopes_final, 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 fastmcp.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: Example:
```python ```python
from fastmcp import FastMCP from fastmcp import FastMCP
from fastmcp.server.auth.providers.google import GoogleProvider from fastmcp.server.auth.providers.google import GoogleDCRProvider
# Simple Google OAuth protection # Simple Google OAuth protection
auth = GoogleProvider( auth = GoogleDCRProvider(
client_id="your-google-client-id.apps.googleusercontent.com", client_id="your-google-client-id.apps.googleusercontent.com",
client_secret="your-google-client-secret" client_secret="your-google-client-secret"
) )
@ -22,16 +22,22 @@ Example:
from __future__ import annotations from __future__ import annotations
import time import time
import warnings
import httpx import httpx
from key_value.aio.protocols import AsyncKeyValue from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings
import fastmcp.settings
from fastmcp.server.auth import TokenVerifier from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken 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.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
)
from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT from fastmcp.utilities.types import NotSet, NotSetT
@ -39,15 +45,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__) logger = get_logger(__name__)
class GoogleProviderSettings(BaseSettings): class GoogleDCRProviderSettings(BaseSettings):
"""Settings for Google OAuth provider.""" """Settings for Google OAuth DCR provider."""
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_GOOGLE_", env_prefix="FASTMCP_SERVER_AUTH_GOOGLE_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_GOOGLE_DCR_", "FASTMCP_SERVER_AUTH_GOOGLE_"],
env_file=ENV_FILE, env_file=ENV_FILE,
extra="ignore", 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_id: str | None = None
client_secret: SecretStr | None = None client_secret: SecretStr | None = None
base_url: AnyHttpUrl | str | None = None base_url: AnyHttpUrl | str | None = None
@ -182,8 +205,8 @@ class GoogleTokenVerifier(TokenVerifier):
return None return None
class GoogleProvider(OAuthProxy): class GoogleDCRProvider(OAuthDCRProxy):
"""Complete Google OAuth provider for FastMCP. """Complete Google OAuth DCR provider for FastMCP.
This provider makes it trivial to add Google OAuth protection to any This provider makes it trivial to add Google OAuth protection to any
FastMCP server. Just provide your Google OAuth app credentials and FastMCP server. Just provide your Google OAuth app credentials and
@ -198,9 +221,9 @@ class GoogleProvider(OAuthProxy):
Example: Example:
```python ```python
from fastmcp import FastMCP 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_id="123456789.apps.googleusercontent.com",
client_secret="GOCSPX-abc123...", client_secret="GOCSPX-abc123...",
base_url="https://my-server.com" base_url="https://my-server.com"
@ -242,7 +265,7 @@ class GoogleProvider(OAuthProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided 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 k: v
for k, v in { for k, v in {
@ -260,20 +283,22 @@ class GoogleProvider(OAuthProxy):
) )
# Validate required settings # Validate required settings
if not settings.client_id: if not provider_settings.client_id:
raise ValueError( 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( 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 # 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 # Google requires at least one scope - openid is the minimal OIDC scope
required_scopes_final = settings.required_scopes or ["openid"] required_scopes_final = provider_settings.required_scopes or ["openid"]
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
# Create Google token verifier # Create Google token verifier
token_verifier = GoogleTokenVerifier( token_verifier = GoogleTokenVerifier(
@ -283,26 +308,45 @@ class GoogleProvider(OAuthProxy):
# Extract secret string from SecretStr # Extract secret string from SecretStr
client_secret_str = ( 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 # Initialize OAuth proxy with Google endpoints
super().__init__( super().__init__(
upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth", upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth",
upstream_token_endpoint="https://oauth2.googleapis.com/token", 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, upstream_client_secret=client_secret_str,
token_verifier=token_verifier, token_verifier=token_verifier,
base_url=settings.base_url, base_url=provider_settings.base_url,
redirect_path=settings.redirect_path, redirect_path=provider_settings.redirect_path,
issuer_url=settings.issuer_url issuer_url=provider_settings.issuer_url
or settings.base_url, # Default to base_url if not specified or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final, allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage, client_storage=client_storage,
) )
logger.info( logger.info(
"Initialized Google OAuth provider for client %s with scopes: %s", "Initialized Google OAuth DCR provider for client %s with scopes: %s",
settings.client_id, provider_settings.client_id,
required_scopes_final, 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 fastmcp.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: 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 2. AuthKitProvider - DCR-compliant provider for WorkOS AuthKit
Choose based on your WorkOS setup and authentication requirements. 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 from __future__ import annotations
import warnings
import httpx import httpx
from key_value.aio.protocols import AsyncKeyValue from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator 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.responses import JSONResponse
from starlette.routing import Route from starlette.routing import Route
import fastmcp.settings
from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier 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.server.auth.providers.jwt import JWTVerifier
from fastmcp.settings import ENV_FILE from fastmcp.settings import (
ENV_FILE,
ExtendedEnvSettingsSource,
ExtendedSettingsConfigDict,
)
from fastmcp.utilities.auth import parse_scopes from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT from fastmcp.utilities.types import NotSet, NotSetT
@ -28,15 +35,32 @@ from fastmcp.utilities.types import NotSet, NotSetT
logger = get_logger(__name__) logger = get_logger(__name__)
class WorkOSProviderSettings(BaseSettings): class WorkOSDCRProviderSettings(BaseSettings):
"""Settings for WorkOS OAuth provider.""" """Settings for WorkOS OAuth DCR provider."""
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_WORKOS_", env_prefix="FASTMCP_SERVER_AUTH_WORKOS_DCR_",
env_prefixes=["FASTMCP_SERVER_AUTH_WORKOS_DCR_", "FASTMCP_SERVER_AUTH_WORKOS_"],
env_file=ENV_FILE, env_file=ENV_FILE,
extra="ignore", 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_id: str | None = None
client_secret: SecretStr | None = None client_secret: SecretStr | None = None
authkit_domain: str | None = None # e.g., "https://your-app.authkit.app" authkit_domain: str | None = None # e.g., "https://your-app.authkit.app"
@ -125,14 +149,14 @@ class WorkOSTokenVerifier(TokenVerifier):
return None return None
class WorkOSProvider(OAuthProxy): class WorkOSDCRProvider(OAuthDCRProxy):
"""Complete WorkOS OAuth provider for FastMCP. """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. It provides OAuth2 authentication for users through WorkOS Connect applications.
Features: Features:
- Transparent OAuth proxy to WorkOS AuthKit - Transparent OAuth DCR proxy to WorkOS AuthKit
- Automatic token validation via userinfo endpoint - Automatic token validation via userinfo endpoint
- User information extraction from ID tokens - User information extraction from ID tokens
- Support for standard OAuth scopes (openid, profile, email) - Support for standard OAuth scopes (openid, profile, email)
@ -146,9 +170,9 @@ class WorkOSProvider(OAuthProxy):
Example: Example:
```python ```python
from fastmcp import FastMCP 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_id="client_123",
client_secret="sk_test_456", client_secret="sk_test_456",
authkit_domain="https://your-app.authkit.app", authkit_domain="https://your-app.authkit.app",
@ -190,7 +214,7 @@ class WorkOSProvider(OAuthProxy):
client_storage: An AsyncKeyValue-compatible store for client registrations, registrations are stored in memory if not provided 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 k: v
for k, v in { for k, v in {
@ -209,31 +233,35 @@ class WorkOSProvider(OAuthProxy):
) )
# Validate required settings # Validate required settings
if not settings.client_id: if not provider_settings.client_id:
raise ValueError( 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( 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( 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 # 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://")): if not authkit_domain_str.startswith(("http://", "https://")):
authkit_domain_str = f"https://{authkit_domain_str}" authkit_domain_str = f"https://{authkit_domain_str}"
authkit_domain_final = authkit_domain_str.rstrip("/") authkit_domain_final = authkit_domain_str.rstrip("/")
timeout_seconds_final = settings.timeout_seconds or 10 timeout_seconds_final = provider_settings.timeout_seconds or 10
scopes_final = settings.required_scopes or [] scopes_final = provider_settings.required_scopes or []
allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris allowed_client_redirect_uris_final = (
provider_settings.allowed_client_redirect_uris
)
# Extract secret string from SecretStr # Extract secret string from SecretStr
client_secret_str = ( 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 # Create WorkOS token verifier
@ -247,26 +275,43 @@ class WorkOSProvider(OAuthProxy):
super().__init__( super().__init__(
upstream_authorization_endpoint=f"{authkit_domain_final}/oauth2/authorize", upstream_authorization_endpoint=f"{authkit_domain_final}/oauth2/authorize",
upstream_token_endpoint=f"{authkit_domain_final}/oauth2/token", 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, upstream_client_secret=client_secret_str,
token_verifier=token_verifier, token_verifier=token_verifier,
base_url=settings.base_url, base_url=provider_settings.base_url,
redirect_path=settings.redirect_path, redirect_path=provider_settings.redirect_path,
issuer_url=settings.issuer_url issuer_url=provider_settings.issuer_url
or settings.base_url, # Default to base_url if not specified or provider_settings.base_url, # Default to base_url if not specified
allowed_client_redirect_uris=allowed_client_redirect_uris_final, allowed_client_redirect_uris=allowed_client_redirect_uris_final,
client_storage=client_storage, client_storage=client_storage,
) )
logger.info( logger.info(
"Initialized WorkOS OAuth provider for client %s with AuthKit domain %s", "Initialized WorkOS OAuth DCR provider for client %s with AuthKit domain %s",
settings.client_id, provider_settings.client_id,
authkit_domain_final, 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 fastmcp.settings.deprecation_warnings:
warnings.warn(
"WorkOSProvider is deprecated, use WorkOSDCRProvider instead",
DeprecationWarning,
stacklevel=2,
)
super().__init__(**kwargs)
class AuthKitProviderSettings(BaseSettings): class AuthKitProviderSettings(BaseSettings):
model_config = SettingsConfigDict( model_config = ExtendedSettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_", env_prefix="FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_",
env_file=ENV_FILE, env_file=ENV_FILE,
extra="ignore", extra="ignore",

View file

@ -0,0 +1,64 @@
"""Test that deprecated provider imports still work.
This test file verifies that the old provider class names (without DCR suffix)
can still be imported, are subclasses of the new DCR providers, and emit the
correct deprecation warnings when instantiated.
"""
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

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ from mcp.shared.auth import InvalidRedirectUriError
from pydantic import AnyUrl from pydantic import AnyUrl
from fastmcp.server.auth.auth import TokenVerifier 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): class MockTokenVerifier(TokenVerifier):
@ -103,7 +103,7 @@ class TestOAuthProxyRedirectValidation:
def test_proxy_default_allows_all(self): def test_proxy_default_allows_all(self):
"""Test that OAuth proxy defaults to allowing all URIs for DCR compatibility.""" """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_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token", upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client", upstream_client_id="test-client",
@ -119,7 +119,7 @@ class TestOAuthProxyRedirectValidation:
"""Test OAuth proxy with custom redirect patterns.""" """Test OAuth proxy with custom redirect patterns."""
custom_patterns = ["http://localhost:*", "https://*.myapp.com/*"] custom_patterns = ["http://localhost:*", "https://*.myapp.com/*"]
proxy = OAuthProxy( proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize", upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token", upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client", upstream_client_id="test-client",
@ -133,7 +133,7 @@ class TestOAuthProxyRedirectValidation:
def test_proxy_empty_list_validation(self): def test_proxy_empty_list_validation(self):
"""Test OAuth proxy with empty list (allow none).""" """Test OAuth proxy with empty list (allow none)."""
proxy = OAuthProxy( proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize", upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token", upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client", upstream_client_id="test-client",
@ -149,7 +149,7 @@ class TestOAuthProxyRedirectValidation:
"""Test that registered clients use the configured patterns.""" """Test that registered clients use the configured patterns."""
custom_patterns = ["https://app.example.com/*"] custom_patterns = ["https://app.example.com/*"]
proxy = OAuthProxy( proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize", upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token", upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client", upstream_client_id="test-client",
@ -181,7 +181,7 @@ class TestOAuthProxyRedirectValidation:
"""Test that unregistered clients return None.""" """Test that unregistered clients return None."""
custom_patterns = ["http://localhost:*", "http://127.0.0.1:*"] custom_patterns = ["http://localhost:*", "http://127.0.0.1:*"]
proxy = OAuthProxy( proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize", upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token", upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="test-client", 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 mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyUrl from pydantic import AnyUrl
from fastmcp.server.auth.oauth_proxy import OAuthProxy from fastmcp.server.auth.oauth_dcr_proxy import OAuthDCRProxy
class TestOAuthProxyStorage: class TestOAuthProxyStorage:
@ -39,9 +39,9 @@ class TestOAuthProxyStorage:
"""Create in-memory storage for testing.""" """Create in-memory storage for testing."""
return MemoryStore() 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.""" """Create an OAuth proxy with specified storage."""
return OAuthProxy( return OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize", upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token", upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-client-id", upstream_client_id="test-client-id",
@ -109,7 +109,7 @@ class TestOAuthProxyStorage:
self, jwt_verifier, temp_storage self, jwt_verifier, temp_storage
): ):
"""Test that ProxyDCRClient is created with redirect URI patterns.""" """Test that ProxyDCRClient is created with redirect URI patterns."""
proxy = OAuthProxy( proxy = OAuthDCRProxy(
upstream_authorization_endpoint="https://github.com/login/oauth/authorize", upstream_authorization_endpoint="https://github.com/login/oauth/authorize",
upstream_token_endpoint="https://github.com/login/oauth/access_token", upstream_token_endpoint="https://github.com/login/oauth/access_token",
upstream_client_id="test-client-id", upstream_client_id="test-client-id",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,645 +0,0 @@
"""Comprehensive tests for OIDC Proxy Provider functionality."""
import json
from unittest.mock import MagicMock, patch
import pytest
from httpx import Response
from pydantic import AnyHttpUrl
from fastmcp.server.auth.oidc_proxy import OIDCConfiguration, OIDCProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
TEST_ISSUER = "https://example.com"
TEST_AUTHORIZATION_ENDPOINT = "https://example.com/authorize"
TEST_TOKEN_ENDPOINT = "https://example.com/oauth/token"
TEST_CONFIG_URL = "https://example.com/.well-known/openid-configuration"
TEST_CLIENT_ID = "test-client-id"
TEST_CLIENT_SECRET = "test-client-secret"
TEST_BASE_URL = "https://example.com:8000/"
# =============================================================================
# Test Fixtures
# =============================================================================
@pytest.fixture
def valid_oidc_configuration_dict():
"""Create a valid OIDC configuration dict for testing."""
return {
"issuer": TEST_ISSUER,
"authorization_endpoint": TEST_AUTHORIZATION_ENDPOINT,
"token_endpoint": TEST_TOKEN_ENDPOINT,
"jwks_uri": "https://example.com/.well-known/jwks.json",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
}
@pytest.fixture
def invalid_oidc_configuration_dict():
"""Create an invalid OIDC configuration dict for testing."""
return {
"issuer": TEST_ISSUER,
"authorization_endpoint": TEST_AUTHORIZATION_ENDPOINT,
"token_endpoint": TEST_TOKEN_ENDPOINT,
"jwks_uri": "https://example.com/.well-known/jwks.json",
}
@pytest.fixture
def valid_google_oidc_configuration_dict():
"""Create a valid Google OIDC configuration dict for testing.
See: https://accounts.google.com/.well-known/openid-configuration
"""
google_config_str = """
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
]
}
"""
return json.loads(google_config_str)
@pytest.fixture
def valid_auth0_oidc_configuration_dict():
"""Create a valid Auth0 OIDC configuration dict for testing.
See: https://<tenant>.us.auth0.com/.well-known/openid-configuration
"""
auth0_config_str = """
{
"issuer": "https://example.us.auth0.com/",
"authorization_endpoint": "https://example.us.auth0.com/authorize",
"token_endpoint": "https://example.us.auth0.com/oauth/token",
"device_authorization_endpoint": "https://example.us.auth0.com/oauth/device/code",
"userinfo_endpoint": "https://example.us.auth0.com/userinfo",
"mfa_challenge_endpoint": "https://example.us.auth0.com/mfa/challenge",
"jwks_uri": "https://example.us.auth0.com/.well-known/jwks.json",
"registration_endpoint": "https://example.us.auth0.com/oidc/register",
"revocation_endpoint": "https://example.us.auth0.com/oauth/revoke",
"scopes_supported": [
"openid",
"profile",
"offline_access",
"name",
"given_name",
"family_name",
"nickname",
"email",
"email_verified",
"picture",
"created_at",
"identities",
"phone",
"address"
],
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token"
],
"code_challenge_methods_supported": [
"S256",
"plain"
],
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"subject_types_supported": [
"public"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"token_endpoint_auth_signing_alg_values_supported": [
"RS256",
"RS384",
"PS256"
],
"claims_supported": [
"aud",
"auth_time",
"created_at",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"identities",
"iss",
"name",
"nickname",
"phone_number",
"picture",
"sub"
],
"request_uri_parameter_supported": false,
"request_parameter_supported": true,
"id_token_signing_alg_values_supported": [
"HS256",
"RS256",
"PS256"
],
"tls_client_certificate_bound_access_tokens": true,
"request_object_signing_alg_values_supported": [
"RS256",
"RS384",
"PS256"
],
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"end_session_endpoint": "https://example.us.auth0.com/oidc/logout",
"backchannel_authentication_endpoint": "https://example.us.auth0.com/bc-authorize",
"backchannel_token_delivery_modes_supported": [
"poll"
],
"global_token_revocation_endpoint": "https://example.us.auth0.com/oauth/global-token-revocation/connection/{connectionName}",
"global_token_revocation_endpoint_auth_methods_supported": [
"global-token-revocation+jwt"
]
}
"""
return json.loads(auth0_config_str)
# =============================================================================
# Test Classes
# =============================================================================
def validate_config(config, source_dict):
"""Validate an OIDC configuration against the source dict."""
for source_key, source_value in source_dict.items():
config_value = getattr(config, source_key, None)
if not hasattr(config, source_key):
continue
config_value = getattr(config, source_key, None)
if isinstance(config_value, AnyHttpUrl):
config_value = str(config_value)
assert config_value == source_value
class TestOIDCConfiguration:
"""Tests for OIDC configuration."""
def test_default_configuration(self, valid_oidc_configuration_dict):
"""Test default configuration with valid dict."""
config = OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
validate_config(config, valid_oidc_configuration_dict)
def test_default_configuration_with_issuer_trailing_slash(
self, valid_oidc_configuration_dict
):
"""Test default configuration with valid dict and issuer trailing slash."""
valid_oidc_configuration_dict["issuer"] += "/"
config = OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
validate_config(config, valid_oidc_configuration_dict)
def test_explicit_strict_configuration(self, valid_oidc_configuration_dict):
"""Test default configuration with explicit True strict setting and valid dict."""
valid_oidc_configuration_dict["strict"] = True
config = OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
validate_config(config, valid_oidc_configuration_dict)
def test_explicit_strict_configuration_with_issuer_trailing_slash(
self, valid_oidc_configuration_dict
):
"""Test default configuration with explicit True strict setting, valid dict and issuer trailing slash."""
valid_oidc_configuration_dict["issuer"] += "/"
config = OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
validate_config(config, valid_oidc_configuration_dict)
def test_default_configuration_raises_error(self, invalid_oidc_configuration_dict):
"""Test default configuration with invalid dict."""
with pytest.raises(ValueError, match="Missing required configuration metadata"):
OIDCConfiguration.model_validate(invalid_oidc_configuration_dict)
def test_explicit_strict_configuration_raises_error(
self, invalid_oidc_configuration_dict
):
"""Test default configuration with explicit True strict setting and invalid dict."""
invalid_oidc_configuration_dict["strict"] = True
with pytest.raises(ValueError, match="Missing required configuration metadata"):
OIDCConfiguration.model_validate(invalid_oidc_configuration_dict)
def test_bad_url_raises_error(self, valid_oidc_configuration_dict):
"""Test default configuration with bad URL setting."""
valid_oidc_configuration_dict["issuer"] = "not-a-URL"
with pytest.raises(ValueError, match="Invalid URL for configuration metadata"):
OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
def test_explict_strict_with_bad_url_raises_error(
self, valid_oidc_configuration_dict
):
"""Test default configuration with explicit True strict setting and bad URL setting."""
valid_oidc_configuration_dict["strict"] = True
valid_oidc_configuration_dict["issuer"] = "not-a-URL"
with pytest.raises(ValueError, match="Invalid URL for configuration metadata"):
OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
def test_not_strict_configuration(self):
"""Test default configuration with explicit False strict setting."""
config = OIDCConfiguration.model_validate({"strict": False})
assert config.issuer is None
assert config.authorization_endpoint is None
assert config.token_endpoint is None
assert config.jwks_uri is None
assert config.response_types_supported is None
assert config.subject_types_supported is None
assert config.id_token_signing_alg_values_supported is None
def test_not_strict_configuration_with_invalid_config(
self, invalid_oidc_configuration_dict
):
"""Test default configuration with explicit False strict setting."""
invalid_oidc_configuration_dict["strict"] = False
config = OIDCConfiguration.model_validate(invalid_oidc_configuration_dict)
validate_config(config, invalid_oidc_configuration_dict)
def test_not_strict_configuration_with_bad_url(self, valid_oidc_configuration_dict):
"""Test default configuration with explicit False strict setting."""
valid_oidc_configuration_dict["strict"] = False
valid_oidc_configuration_dict["issuer"] = "not-a-url"
config = OIDCConfiguration.model_validate(valid_oidc_configuration_dict)
validate_config(config, valid_oidc_configuration_dict)
def test_google_configuration(self, valid_google_oidc_configuration_dict):
"""Test Google configuration."""
config = OIDCConfiguration.model_validate(valid_google_oidc_configuration_dict)
validate_config(config, valid_google_oidc_configuration_dict)
def test_auth0_configuration(self, valid_auth0_oidc_configuration_dict):
"""Test Auth0 configuration."""
config = OIDCConfiguration.model_validate(valid_auth0_oidc_configuration_dict)
validate_config(config, valid_auth0_oidc_configuration_dict)
def validate_get_oidc_configuration(oidc_configuration, strict, timeout_seconds):
"""Validate get_oidc_configuation call."""
with patch("httpx.get") as mock_get:
mock_response = MagicMock(spec=Response)
mock_response.json.return_value = oidc_configuration
mock_get.return_value = mock_response
config = OIDCConfiguration.get_oidc_configuration(
config_url=AnyHttpUrl(TEST_CONFIG_URL),
strict=strict,
timeout_seconds=timeout_seconds,
)
validate_config(config, oidc_configuration)
mock_get.assert_called_once()
call_args = mock_get.call_args
assert call_args[0][0] == TEST_CONFIG_URL
return call_args
class TestGetOIDCConfiguration:
"""Tests for getting OIDC configuration."""
def test_get_oidc_configuration(self, valid_oidc_configuration_dict):
"""Test with valid response and explicit timeout."""
call_args = validate_get_oidc_configuration(
valid_oidc_configuration_dict, True, 10
)
assert call_args[1]["timeout"] == 10
def test_get_oidc_configuration_no_timeout(self, valid_oidc_configuration_dict):
"""Test with valid response and no timeout."""
call_args = validate_get_oidc_configuration(
valid_oidc_configuration_dict, True, None
)
assert "timeout" not in call_args[1]
def test_get_oidc_configuration_raises_error(
self, invalid_oidc_configuration_dict
) -> None:
"""Test with invalid response."""
with pytest.raises(ValueError, match="Missing required configuration metadata"):
validate_get_oidc_configuration(invalid_oidc_configuration_dict, True, 10)
def test_get_oidc_configuration_not_strict(
self, invalid_oidc_configuration_dict
) -> None:
"""Test with invalid response and strict set to False."""
with patch("httpx.get") as mock_get:
mock_response = MagicMock(spec=Response)
mock_response.json.return_value = invalid_oidc_configuration_dict
mock_get.return_value = mock_response
OIDCConfiguration.get_oidc_configuration(
config_url=AnyHttpUrl(TEST_CONFIG_URL),
strict=False,
timeout_seconds=10,
)
mock_get.assert_called_once()
call_args = mock_get.call_args
assert call_args[0][0] == TEST_CONFIG_URL
def validate_proxy(mock_get, proxy, oidc_config):
"""Validate OIDC proxy."""
mock_get.assert_called_once()
call_args = mock_get.call_args
assert str(call_args[0][0]) == TEST_CONFIG_URL
assert proxy._upstream_authorization_endpoint == TEST_AUTHORIZATION_ENDPOINT
assert proxy._upstream_token_endpoint == TEST_TOKEN_ENDPOINT
assert proxy._upstream_client_id == TEST_CLIENT_ID
assert proxy._upstream_client_secret.get_secret_value() == TEST_CLIENT_SECRET
assert str(proxy.base_url) == TEST_BASE_URL
assert proxy.oidc_config == oidc_config
class TestOIDCProxyInitialization:
"""Tests for OIDC proxy initialization."""
def test_default_initialization(self, valid_oidc_configuration_dict):
"""Test default initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
proxy = OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
)
validate_proxy(mock_get, proxy, oidc_config)
def test_timeout_seconds_initialization(self, valid_oidc_configuration_dict):
"""Test timeout seconds initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
proxy = OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
timeout_seconds=12,
)
validate_proxy(mock_get, proxy, oidc_config)
call_args = mock_get.call_args
assert call_args[1]["timeout_seconds"] == 12
def test_token_verifier_initialization(self, valid_oidc_configuration_dict):
"""Test token verifier initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
proxy = OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
algorithm="RS256",
audience="oidc-proxy-test-audience",
required_scopes=["required", "scopes"],
)
validate_proxy(mock_get, proxy, oidc_config)
assert isinstance(proxy._token_validator, JWTVerifier)
assert proxy._token_validator.algorithm == "RS256"
assert proxy._token_validator.audience == "oidc-proxy-test-audience"
assert proxy._token_validator.required_scopes == ["required", "scopes"]
def test_extra_parameters_initialization(self, valid_oidc_configuration_dict):
"""Test other parameters initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
proxy = OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
audience="oidc-proxy-test-audience",
)
validate_proxy(mock_get, proxy, oidc_config)
assert proxy._extra_authorize_params == {
"audience": "oidc-proxy-test-audience"
}
assert proxy._extra_token_params == {"audience": "oidc-proxy-test-audience"}
def test_other_parameters_initialization(self, valid_oidc_configuration_dict):
"""Test other parameters initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
proxy = OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
redirect_path="/oidc/proxy",
allowed_client_redirect_uris=["http://localhost:*"],
token_endpoint_auth_method="client_secret_post",
)
validate_proxy(mock_get, proxy, oidc_config)
assert proxy._redirect_path == "/oidc/proxy"
assert proxy._allowed_client_redirect_uris == ["http://localhost:*"]
assert proxy._token_endpoint_auth_method == "client_secret_post"
def test_no_config_url_initialization_raises_error(
self, valid_oidc_configuration_dict
):
"""Test no config URL initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
with pytest.raises(ValueError, match="Missing required config URL"):
OIDCProxy(
config_url=None, # type: ignore
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
)
def test_no_client_id_initialization_raises_error(
self, valid_oidc_configuration_dict
):
"""Test no client id initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
with pytest.raises(ValueError, match="Missing required client id"):
OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=None, # type: ignore
client_secret=TEST_CLIENT_SECRET,
base_url=TEST_BASE_URL,
)
def test_no_client_secret_initialization_raises_error(
self, valid_oidc_configuration_dict
):
"""Test no client secret initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
with pytest.raises(ValueError, match="Missing required client secret"):
OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=None, # type: ignore
base_url=TEST_BASE_URL,
)
def test_no_base_url_initialization_raises_error(
self, valid_oidc_configuration_dict
):
"""Test no base URL initialization."""
with patch(
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
) as mock_get:
oidc_config = OIDCConfiguration.model_validate(
valid_oidc_configuration_dict
)
mock_get.return_value = oidc_config
with pytest.raises(ValueError, match="Missing required base URL"):
OIDCProxy(
config_url=TEST_CONFIG_URL,
client_id=TEST_CLIENT_ID,
client_secret=TEST_CLIENT_SECRET,
base_url=None, # type: ignore
)