The auth option uses FastMCP’s built-in OAuth Proxy that acts as a secure intermediary between MCP clients and upstream OAuth providers. The proxy handles the complete OAuth 2.1 authorization flow, including Dynamic Client Registration (DCR), PKCE, consent management, and token management with encryption and token swap patterns enabled by default.
Key Features
Secure by Default : Automatic encryption (AES-256-GCM) and token swap pattern
Zero Configuration : Auto-generates keys and handles OAuth flows automatically
Pre-configured Providers : Built-in support for Google, GitHub, and Azure
RFC Compliant : Implements DCR (RFC 7591), PKCE, and OAuth 2.1
Optional JWKS : Support for RS256/ES256 token verification (via optional jose dependency)
Quick Start
The simplest way to use the OAuth Proxy is through the auth option with a pre-configured provider:
import { FastMCP , getAuthSession , GoogleProvider , requireAuth } from "fastmcp" ;
const server = new FastMCP ({
auth: new GoogleProvider ({
baseUrl: "https://your-server.com" ,
clientId: process . env . GOOGLE_CLIENT_ID ! ,
clientSecret: process . env . GOOGLE_CLIENT_SECRET ! ,
}),
name: "My Server" ,
version: "1.0.0" ,
});
server . addTool ({
canAccess: requireAuth ,
name: "protected-tool" ,
execute : async ( _args , { session }) => {
const { accessToken } = getAuthSession ( session );
// Use accessToken to call upstream APIs
return "Authenticated!" ;
},
});
That’s it! All OAuth endpoints are automatically available:
/oauth/register - Dynamic Client Registration
/oauth/authorize - Authorization endpoint
/oauth/callback - OAuth callback handler
/oauth/consent - User consent screen
/oauth/token - Token exchange endpoint
Google
GitHub
Azure
Custom
import { GoogleProvider } from "fastmcp" ;
const server = new FastMCP ({
auth: new GoogleProvider ({
baseUrl: "https://your-server.com" ,
clientId: process . env . GOOGLE_CLIENT_ID ! ,
clientSecret: process . env . GOOGLE_CLIENT_SECRET ! ,
scopes: [ "openid" , "profile" , "email" ],
}),
name: "My Server" ,
version: "1.0.0" ,
});
Setup:
Go to Google Cloud Console
Create OAuth 2.0 Client ID
Add redirect URI: https://your-server.com/oauth/callback
import { GitHubProvider } from "fastmcp" ;
const server = new FastMCP ({
auth: new GitHubProvider ({
baseUrl: "https://your-server.com" ,
clientId: process . env . GITHUB_CLIENT_ID ! ,
clientSecret: process . env . GITHUB_CLIENT_SECRET ! ,
scopes: [ "read:user" , "user:email" ],
}),
name: "My Server" ,
version: "1.0.0" ,
});
Setup:
Go to GitHub Developer Settings
Click “New OAuth App”
Set callback URL: https://your-server.com/oauth/callback
import { AzureProvider } from "fastmcp" ;
const server = new FastMCP ({
auth: new AzureProvider ({
baseUrl: "https://your-server.com" ,
clientId: process . env . AZURE_CLIENT_ID ! ,
clientSecret: process . env . AZURE_CLIENT_SECRET ! ,
tenantId: "common" , // or specific tenant ID
}),
name: "My Server" ,
version: "1.0.0" ,
});
Setup:
Go to Azure Portal
Click “New registration”
Add redirect URI: https://your-server.com/oauth/callback
import { OAuthProvider } from "fastmcp" ;
const server = new FastMCP ({
auth: new OAuthProvider ({
authorizationEndpoint: "https://provider.com/oauth/authorize" ,
baseUrl: "https://your-server.com" ,
clientId: process . env . OAUTH_CLIENT_ID ! ,
clientSecret: process . env . OAUTH_CLIENT_SECRET ! ,
scopes: [ "openid" , "profile" ],
tokenEndpoint: "https://provider.com/oauth/token" ,
}),
name: "My Server" ,
version: "1.0.0" ,
});
Use this for SAP, Auth0, Okta, or any OAuth 2.0 provider.
Advanced Configuration
For more control over OAuth behavior, use the oauth option directly with an OAuthProxy:
import { FastMCP } from "fastmcp" ;
import { OAuthProxy } from "fastmcp/auth" ;
const authProxy = new OAuthProxy ({
upstreamAuthorizationEndpoint: "https://provider.com/oauth/authorize" ,
upstreamTokenEndpoint: "https://provider.com/oauth/token" ,
upstreamClientId: process . env . OAUTH_CLIENT_ID ! ,
upstreamClientSecret: process . env . OAUTH_CLIENT_SECRET ! ,
baseUrl: "https://your-server.com" ,
scopes: [ "openid" , "profile" ],
});
const server = new FastMCP ({
name: "My Server" ,
oauth: {
enabled: true ,
authorizationServer: authProxy . getAuthorizationServerMetadata (),
proxy: authProxy ,
},
});
Token Swap Pattern
Token swap prevents upstream tokens from reaching the client. This is enabled by default for enhanced security.
import { OAuthProxy , DiskStore , JWTIssuer } from "fastmcp/auth" ;
const authProxy = new OAuthProxy ({
baseUrl: "https://your-server.com" ,
upstreamAuthorizationEndpoint: "https://provider.com/oauth/authorize" ,
upstreamTokenEndpoint: "https://provider.com/oauth/token" ,
upstreamClientId: process . env . OAUTH_CLIENT_ID ,
upstreamClientSecret: process . env . OAUTH_CLIENT_SECRET ,
// Token swap is enabled by default
// Optionally provide your own signing key (recommended for production)
jwtSigningKey: await JWTIssuer . deriveKey ( process . env . JWT_SECRET , 100000 ),
// Use persistent storage
tokenStorage: new DiskStore ({
directory: "/var/lib/fastmcp/oauth" ,
}),
});
If you don’t provide jwtSigningKey, one will be auto-generated. For production, it’s recommended to provide your own derived key for consistency across server restarts.
Loading Upstream Tokens
When using token swap, load the upstream tokens in your tools:
server . addTool ({
name: "call-api" ,
description: "Call upstream API with user's token" ,
execute : async ( args , { session }) => {
const clientToken = session ?. headers ?.[ "authorization" ]?. replace (
"Bearer " ,
"" ,
);
// Load the upstream tokens
const upstreamTokens = await authProxy . loadUpstreamTokens ( clientToken );
if ( upstreamTokens ) {
const response = await fetch ( "https://api.provider.com/user" , {
headers: {
Authorization: `Bearer ${ upstreamTokens . accessToken } ` ,
},
});
const data = await response . json ();
return {
content: [{ type: "text" , text: JSON . stringify ( data ) }],
};
}
throw new Error ( "No valid token" );
},
});
Persistent Token Storage
Use DiskStore for production deployments:
import { DiskStore } from "fastmcp/auth" ;
const storage = new DiskStore ({
directory: "/var/lib/fastmcp/oauth" ,
cleanupIntervalMs: 60000 , // Cleanup every minute
fileExtension: ".json" ,
});
const authProxy = new OAuthProxy ({
// ... other config
tokenStorage: storage ,
});
Benefits:
Tokens persist across server restarts
Automatic cleanup of expired entries
Thread-safe concurrent operations
Encrypted Token Storage
Storage is automatically encrypted with AES-256-GCM:
import { DiskStore , JWTIssuer } from "fastmcp/auth" ;
const authProxy = new OAuthProxy ({
// ... other config
tokenStorage: new DiskStore ({ directory: "/var/lib/fastmcp/oauth" }),
// ← Automatically encrypted!
// Optional: Provide custom encryption key (recommended for production)
encryptionKey: await JWTIssuer . deriveKey (
process . env . ENCRYPTION_SECRET + ":storage" ,
100000 ,
),
});
To disable encryption (only for development/testing): const authProxy = new OAuthProxy ({
tokenStorage: new MemoryTokenStorage (),
encryptionKey: false , // Explicitly disable encryption
});
Custom Claims Passthrough
Pass custom claims from upstream tokens (roles, permissions, etc.) to your proxy-issued JWTs. Enabled by default :
import { OAuthProxy } from "fastmcp/auth" ;
const authProxy = new OAuthProxy ({
// ... other config
customClaimsPassthrough: {
// Extract from access token (default: true)
fromAccessToken: true ,
// Extract from ID token (default: true)
fromIdToken: true ,
// No prefix by default for RBAC compatibility
claimPrefix: false ,
// Optional: Only allow specific claims
allowedClaims: [ "role" , "roles" , "permissions" , "email" , "groups" ],
// Optional: Block specific claims
blockedClaims: [ "internal_id" , "debug_info" ],
// Maximum claim value size (default: 2000 chars)
maxClaimValueSize: 2000 ,
// Allow complex objects/arrays (default: false)
allowComplexClaims: false ,
},
});
Using Claims for Authorization
server . addTool ({
name: "admin-dashboard" ,
description: "Access admin dashboard" ,
canAccess : async ({ session }) => {
const token = session ?. headers ?.[ "authorization" ]?. replace ( "Bearer " , "" );
if ( ! token ) return false ;
// Decode the proxy JWT
const payload = JSON . parse (
Buffer . from ( token . split ( "." )[ 1 ], "base64url" ). toString (),
);
// Check role claim from upstream IDP
return payload . role === "admin" || payload . roles ?. includes ( "admin" );
},
execute : async () => {
return {
content: [{ type: "text" , text: "Admin dashboard data..." }],
};
},
});
Configuration Options
Option Type Default Description upstreamAuthorizationEndpointstring- Required - OAuth provider’s authorization endpointupstreamTokenEndpointstring- Required - OAuth provider’s token endpointupstreamClientIdstring- Required - Your OAuth client IDupstreamClientSecretstring- Required - Your OAuth client secretbaseUrlstring- Required - Your server’s base URLredirectPathstring/oauth/callbackOAuth callback path scopesstring[]Provider defaults OAuth scopes to request forwardPkcebooleanfalseForward PKCE to upstream provider consentRequiredbooleantrueShow consent screen enableTokenSwapbooleantrueEnable token swap pattern jwtSigningKeystringAuto-generated JWT signing key encryptionKeystring | falseAuto-generated Storage encryption key tokenStorageTokenStorageMemoryTokenStorageToken storage backend transactionTtlnumber600Transaction TTL (seconds) authorizationCodeTtlnumber300Auth code TTL (seconds) accessTokenTtlnumber3600Access token TTL (seconds) refreshTokenTtlnumber2592000Refresh token TTL (seconds)
Security Best Practices
Use HTTPS in Production
const authProxy = new OAuthProxy ({
baseUrl: "https://your-server.com" , // Not http://
// ...
});
Derive Keys from Secrets
import { JWTIssuer } from "fastmcp/auth" ;
const jwtSigningKey = await JWTIssuer . deriveKey (
process . env . JWT_SECRET ,
100000 , // PBKDF2 iterations
);
const encryptionKey = await JWTIssuer . deriveKey (
process . env . ENCRYPTION_SECRET ,
100000 ,
);
Use Different Keys for Different Purposes
const jwtKey = await JWTIssuer . deriveKey ( process . env . SECRET + ":jwt" , 100000 );
const storageKey = await JWTIssuer . deriveKey ( process . env . SECRET + ":storage" , 100000 );
const consentKey = await JWTIssuer . deriveKey ( process . env . SECRET + ":consent" , 100000 );
Enable Consent Screen
const authProxy = new OAuthProxy ({
consentRequired: true , // Default, but be explicit
// ...
});
Use Persistent Encrypted Storage
const storage = new DiskStore ({
directory: "/var/lib/fastmcp/oauth"
});
const authProxy = new OAuthProxy ({
tokenStorage: storage ,
encryptionKey: await JWTIssuer . deriveKey ( process . env . ENCRYPTION_SECRET , 100000 ),
// ...
});
Validate Redirect URIs
const authProxy = new OAuthProxy ({
allowedRedirectUriPatterns: [
"https://yourdomain.com/*" ,
"http://localhost:*" , // Only for development
],
// ...
});
Next Steps
Authentication Learn about authentication options and tool authorization
Custom Routes Add authenticated custom HTTP routes to your server