This guide shows how to implement OAuth 2.1 PKCE authentication for Tiny MCP that works with FastAPI Server's existing IdP (Identity Provider) OIDC integration. This authentication mechanism is independent of TinyMCP. The main thing to note from the TinyMCP mcp router are: the specific MCP routes and some specific endpoints for OAuth discovery, registration and overall authentication process.
This implementation enables MCP clients (like Cursor) to authenticate using OAuth 2.1 PKCE via FastAPI Server's existing OAuth integration with IdPs.
Key Components:
- Resource Discovery - OAuth protected resource metadata
- Authorization Server Discovery - OAuth 2.1 server metadata
- Dynamic Client Registration - Ephemeral client registration in Redis
- PKCE Authorization Flow - With intermediate redirect handling
- Token Exchange - Proxy to IdP for token validation
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ MCP Client │ │ FastAPI │ │ IdP │
│ (Cursor) │ │ Server │ │ │
└──────┬──────┘ └──────┬───────┘ └─────────┬───────┘
│ │ │
1. │ GET /mcp │ │
├─────────────────►│ 401 + Discovery │
│ │ │
2. │ /.well-known/* │ │
├─────────────────►│ Resource & Server │
│ │ Discovery │
3. │ POST /register │ │
├─────────────────►│ Ephemeral client │
│ │ (Redis TTL) │
4. │ /authorize+PKCE │ │
├─────────────────►├─────────────────────►│
│ │ Intermediate redirect│
5. │ │◄─────────────────────┤
│ Relay to client │ Code to /callback │
│◄─────────────────┤ │
│ │ │
6. │ POST /token │ │
├─────────────────►├─────────────────────►│
│ │ Code + verifier │
7. │ JWT Token │◄─────────────────────┤
│◄─────────────────┤ │
│ │ │
8. │ /mcp + Bearer │ │
├─────────────────►│ Validate & Forward │
│ │ │
The MCP client sends a request to a protected resource at the designated router (e.g., /mcp) in FastAPI Server.
FastAPI Server's authentication middleware intercepts the unauthenticated request and responds with:
- HTTP 401 Unauthorized
- WWW-Authenticate header with metadata discovery location (
resource_metadata)
This triggers OAuth discovery on the MCP Client's side.
The MCP client discovers FastAPI Server as resource server via:
/.well-known/oauth-protected-resource
This endpoint advertises FastAPI Server as the resource server for protected MCP endpoints.
The MCP client follows the discovery pattern and calls:
/.well-known/oauth-authorization-server
The response includes key metadata (e.g.,):
authorization_endpoint:/auth-mcp/authorizetoken_endpoint:/auth-mcp/tokenregistration_endpoint:/auth-mcp/register
MCP client registers with FastAPI Server via:
POST /auth-mcp/register
- Provides its
redirect_uri(e.g.,cursor://auth/callback) - Receives ephemeral
client_id(andclient_secretif required)
The client calls /auth-mcp/authorize some info for the auth to continue. These includes:
code_challenge(MCP client generates this, which is a hash of a uniquecode_verifierstring stored at the client side. MCP client sends back thiscode_verifierwhen calling/token)code_challenge_methodredirect_uri(its own callback endpoint, specific to the client/tool, e.g., cursor url for specific mcp tool)
On receiving this request, FastAPI Server:
- Creates a unique string for state persistence throughout the request cycle
- Stores the redirect_uri (e.g., cursor mcp tool url) and state in Redis (temporary state store)
- Defines an intermediate redirect URI within the FastAPI Server server (e.g.,
/auth-mcp/oauth/callback) to receive the code from IdP - Constructs a new authorization request and redirects the user to the IDP (IdP) using this intermediate callback URI
IdP completes user authentication and returns the authorization code by redirecting the browser to the intermediate callback URI:
/auth-mcp/oauth/callback
FastAPI Server server then:
- Retrieves the original redirect_uri from Redis
- Redirects the client to its original redirect URI (e.g. cursor mcp tool url), appending the received code and state in the query param
The MCP client then calls:
POST /auth-mcp/token
Including the code and the original code_verifier
FastAPI Server:
- Exchanges the code + code_verifier with IdP for a valid
access_token(JWT). This endpoint acts as a proxy, calling IdP's/tokenendpoint internally - The access token is returned to the MCP client
The client caches the token (e.g., in local storage) and uses it in a Bearer token header to call:
/mcp... endpoints (i.e., tool APIs)
The middleware validates the token and forwards the request to the FastAPI's MCP router for tool calling.
| Endpoint | Purpose |
|---|---|
/mcp |
MCP endpoints that require authentication |
/.well-known/oauth-protected-resource |
Resource server discovery endpoint, advertising FastAPI Server as the resource server for protected MCP endpoints |
/.well-known/oauth-authorization-server |
OAuth 2.1 metadata (authorization/token/register URLs) |
/auth-mcp/register |
Dynamic client registration endpoint, allowing MCP clients to register their unique redirect_uris and receive ephemeral client_id and client_secret |
/auth-mcp/authorize |
Handles PKCE initiation, stores state and redirect_uri, redirects to IDP |
/auth-mcp/oauth/callback |
Intermediary to receive auth code from IDP, re-redirects to client |
/auth-mcp/token |
Accepts code + code_verifier, exchanges for token via IDP. Acts as a proxy, calling IdP's /token endpoint internally |
Problem: Load balancers may drop X-Forwarded-Proto headers during internal redirects, causing HTTPS URLs to become HTTP.
Solution:
from fastapi.middleware.trustedhost import TrustedHostMiddleware
# ProxyHeadersMiddleware with trusted hosts
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*"])Problem: OAuth state needs to be maintained across redirects.
Solution: Redis-based state storage with TTL:
redis_client.setex(f"oauth_state:{state}", 600, json.dumps(state_data))
redis_client.setex(f"oauth_redirect:{state}", 600, redirect_uri)Problem: IdP (IDP) is not aware of the per-mcp tool redirect URIs used by various MCP clients and tools. Redirecting directly from the IDP to the MCP client hence is not workable.
Solution: This is addressed by using an internal intermediate redirect URI (/auth-mcp/oauth/callback) within FastAPI Server. This endpoint:
- Acts as a relay that can receive the auth code from IdP
- Uses the stored state to retrieve the original client-specific redirect_uri from Redis
- Then performs a second redirect to the actual MCP client URI with the correct code and state
This approach prevents IdP from needing to know or whitelist every client callback URI while maintaining the desired OAuth flow.