RFC-compliant Azure AD OAuth 2.0 router for FastAPI MCP servers with Copilot Studio support
Project description
fastapi-mcp-azure-oauth
RFC-compliant Azure AD OAuth 2.0 router for FastAPI MCP servers, with first-class support for Copilot Studio and other Azure AD clients.
What it does
Drop a single call into any FastAPI application to get:
| Standard | Endpoint | Purpose |
|---|---|---|
| RFC 8414 | GET /.well-known/oauth-authorization-server |
Delegates clients to Azure AD's real auth endpoints |
| RFC 7591 | GET /register |
Returns app credentials (Copilot Studio GET-variant) |
| RFC 7591 | POST /register |
Dynamic Client Registration + auto Azure AD URI enrolment |
| RFC 9728 | GET /.well-known/oauth-protected-resource/{slug} |
Protected resource metadata for MCP autodiscovery |
| — | GET /oauth/callback |
Minimal callback (echoes code + state for client-side exchange) |
| — | GET /oauth/config |
MSAL-compatible configuration for browser clients |
Plus a TokenValidator that:
- Verifies Azure AD JWT signatures via JWKS with per-tenant key caching
- Supports both single-tenant and multi-tenant (
/organizations) deployments - Enforces explicit issuer binding after signature verification (closes PyJWT
verify_issno-op gap) - Rejects
api://{app_id}/.defaultas an audience (it's a scope suffix, not a valid token audience) - Caps the JWKS client cache at 50 tenants with FIFO eviction
Installation
pip install fastapi-mcp-azure-oauth
Requires Python 3.10+ and FastAPI 0.115+.
Quick start
from fastapi import FastAPI, Depends
from fastapi_mcp_azure_oauth import build_oauth_router, TokenValidator
app = FastAPI()
# 1 — Mount the OAuth router (all RFC-required endpoints)
app.include_router(
build_oauth_router(
app_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # Azure AD App (client) ID
tenant_id="yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy", # Home tenant ID
client_secret="your-client-secret",
api_scope="access_as_user", # exposed under api://{app_id}/
resource_path="/mcp", # your protected resource path
)
)
# 2 — Validate incoming Bearer tokens on protected endpoints
validator = TokenValidator(app_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
@app.post("/mcp")
async def mcp_handler(claims: dict = Depends(validator.as_dependency)):
user_id = validator.get_user_id(claims)
return {"user": user_id}
Configuration reference
build_oauth_router()
| Parameter | Type | Default | Description |
|---|---|---|---|
app_id |
str |
required | Azure AD Application (client) ID |
tenant_id |
str |
required | Home tenant ID — used for Graph API calls and single-tenant discovery. Pass the home tenant even in multi-tenant deployments. |
client_secret |
str |
required | Azure AD client secret — used for Graph API calls and returned in DCR responses |
api_scope |
str |
"access_as_user" |
Scope name under api://{app_id}/ |
resource_path |
str |
"/mcp" |
Path to your protected resource — drives /.well-known slugs and the resource field |
allowed_tenant_ids |
list[str] | None |
None |
Restrict discovery to specific tenants. None advertises /organizations. |
config_redirect_uri_path |
str |
"/oauth/callback" |
Server-relative path returned as redirect_uri in GET /oauth/config |
TokenValidator()
| Parameter | Type | Default | Description |
|---|---|---|---|
app_id |
str |
required | Azure AD Application (client) ID |
allowed_tenant_ids |
list[str] | None |
None |
Restrict token acceptance. None accepts all Azure AD tenants. |
How it works
Client This server Azure AD / Graph
│ │ │
│ GET /.well-known/... │ │
│──────────────────────────>│ │
│<── auth/token endpoints ──│ (points at Azure AD) │
│ │ │
│ POST /register │ │
│──────────────────────────>│ POST /oauth2/token ─────>│
│ │<── access_token ──────────│
│ │ PATCH /applications ─────>│
│<── client_id + secret ────│<── 204 ───────────────────│
│ │ │
│ GET /authorize (→ AAD) │ │
│──────────────────────────────────────────────────────>│
│<────────────────────── code ──────────────────────────│
│ POST /token (→ AAD) │ │
│──────────────────────────────────────────────────────>│
│<──────────────── access_token ────────────────────────│
│ │ │
│ POST /mcp │ │
│ Authorization: Bearer .. │ │
│──────────────────────────>│ GET /discovery/v2.0/keys>│
│ │<── JWKS ──────────────────│
│ │ Verify signature │
│ │ Check iss binding │
│ │ Check aud │
│<─── MCP response ─────────│ │
Step 3 (auth code flow) and step 4 (token exchange) happen entirely on Microsoft's side — this server is not involved.
Azure AD app registration requirements
- Register an app in Azure AD / Entra ID.
- Create a Client secret and note it.
- Under Expose an API, add a scope (e.g.
access_as_user). - Grant the app
Application.ReadWrite.OwnedByMicrosoft Graph permission (for automatic redirect URI enrolment viaPOST /register). UseApplication.ReadWrite.Allif the app doesn't own itself in your tenant. - Under Authentication, add the following as SPA redirect URIs:
https://your-server/oauth/callback- Any other redirect URIs your clients use
Multi-tenant deployments
Pass allowed_tenant_ids=None (the default) and use tenant_id as your app's home tenant:
build_oauth_router(
app_id="...",
tenant_id="your-home-tenant-id", # used for Graph API only
client_secret="...",
allowed_tenant_ids=None, # accept tokens from any AAD tenant
)
validator = TokenValidator(
app_id="...",
allowed_tenant_ids=None, # accept tokens from any AAD tenant
)
To restrict to a specific set of tenants:
validator = TokenValidator(
app_id="...",
allowed_tenant_ids=["tenant-a", "tenant-b"],
)
Contributing
See CONTRIBUTING.md. All contributions are welcome.
Security
Please report security vulnerabilities privately. See SECURITY.md.
License
MIT © 2026 Lee Pasifull
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapi_mcp_azure_oauth-1.0.1.tar.gz.
File metadata
- Download URL: fastapi_mcp_azure_oauth-1.0.1.tar.gz
- Upload date:
- Size: 18.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1c54f612d59a58bac169b87537bf8ab1ea878d9f20682a460df329efbaa0d3a
|
|
| MD5 |
c5e78bde944518264d47994e4b4ae47f
|
|
| BLAKE2b-256 |
a984cce76ec37c57031d51bb4721e1c8f566bfbe6cdaacf418a93c20ee9db13c
|
File details
Details for the file fastapi_mcp_azure_oauth-1.0.1-py3-none-any.whl.
File metadata
- Download URL: fastapi_mcp_azure_oauth-1.0.1-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c98a7e4c78bbfb2e17b5a4aa246faee818ed0d4848be680e5469333e7c430ac4
|
|
| MD5 |
3999e145575eb6f0df667562d0fbdef9
|
|
| BLAKE2b-256 |
d40878263fbb783bd5f9a2dec12b03672cd95d780736bf7ec3df037168f4cce7
|