Skip to main content
The OAuthProxy class provides a transparent OAuth 2.1 proxy that acts as an intermediary between MCP clients and upstream OAuth providers. It implements Dynamic Client Registration (DCR) and supports both token swap and pass-through patterns.

Constructor

Create a new OAuthProxy instance.
import { OAuthProxy } from "fastmcp";

const proxy = new OAuthProxy({
  baseUrl: "http://localhost:8000",
  upstreamAuthorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
  upstreamTokenEndpoint: "https://oauth2.googleapis.com/token",
  upstreamClientId: process.env.GOOGLE_CLIENT_ID!,
  upstreamClientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  scopes: ["openid", "profile", "email"],
});

Configuration

baseUrl
string
required
Base URL of the proxy server (e.g., “https://api.example.com”)
upstreamAuthorizationEndpoint
string
required
Upstream OAuth provider’s authorization endpoint URL
upstreamTokenEndpoint
string
required
Upstream OAuth provider’s token endpoint URL
upstreamClientId
string
required
Pre-registered client ID with upstream provider
upstreamClientSecret
string
required
Pre-registered client secret with upstream provider
scopes
string[]
OAuth scopes to request from upstream provider
enableTokenSwap
boolean
default:"true"
Enable token swap pattern (issues short-lived JWTs instead of passing through upstream tokens)
  • When true: Issues short-lived FastMCP JWTs and stores upstream tokens securely
  • When false: Returns upstream tokens directly to clients
Require user consent screen before authorizing
redirectPath
string
default:"/oauth/callback"
OAuth callback path (relative to baseUrl)
allowedRedirectUriPatterns
string[]
default:"[\"https://*\", \"http://localhost:*\"]"
Allowed redirect URI patterns for client registration (supports wildcards)
upstreamTokenEndpointAuthMethod
'client_secret_basic' | 'client_secret_post'
default:"client_secret_basic"
Authentication method for upstream token endpoint
  • client_secret_basic: Credentials in Authorization header (RFC 6749 Section 2.3.1)
  • client_secret_post: Credentials in request body
accessTokenTtl
number
default:"3600"
Access token TTL in seconds (for token swap mode)
refreshTokenTtl
number
default:"2592000"
Refresh token TTL in seconds (30 days, for token swap mode)
authorizationCodeTtl
number
default:"300"
Authorization code TTL in seconds (5 minutes)
transactionTtl
number
default:"600"
OAuth transaction TTL in seconds (10 minutes)
jwtSigningKey
string
Secret key for signing JWTs (auto-generated if not provided, required for token swap mode)
encryptionKey
string | false
Encryption key for token storage (auto-generated if not provided, set to false to disable)
Secret key for signing consent cookies (auto-generated if not provided)
tokenStorage
TokenStorage
Custom token storage backend (defaults to encrypted MemoryTokenStorage)
customClaimsPassthrough
boolean | CustomClaimsPassthroughConfig
default:"true"
Extract custom claims from upstream tokens and include them in proxy JWTs
// Enable with default settings
customClaimsPassthrough: true

// Fine-grained configuration
customClaimsPassthrough: {
  allowedClaims: ["role", "permissions"], // Only these claims
  blockedClaims: ["internal_id"],         // Never these claims
  claimPrefix: "upstream_",               // Prefix to prevent collisions
  fromAccessToken: true,                   // Extract from access token
  fromIdToken: true,                       // Extract from ID token
  allowComplexClaims: false,               // Only primitives
  maxClaimValueSize: 2000,                 // Max value length
}
forwardPkce
boolean
default:"false"
Forward client’s PKCE to upstream provider (experimental)

Methods

registerClient()

Handle Dynamic Client Registration (RFC 7591) request.
const registration = await proxy.registerClient({
  redirect_uris: ["http://localhost:3000/callback"],
  client_name: "My MCP Client",
  grant_types: ["authorization_code", "refresh_token"],
});

console.log(registration.client_id);
console.log(registration.client_secret);
request
DCRRequest
required
response
DCRResponse

authorize()

Handle OAuth authorization request.
const response = await proxy.authorize({
  client_id: "client-123",
  redirect_uri: "http://localhost:3000/callback",
  response_type: "code",
  scope: "openid profile email",
  state: "random-state",
  code_challenge: "...",
  code_challenge_method: "S256",
});

// Returns redirect to upstream provider or consent screen
params
AuthorizationParams
required
response
Response
HTTP redirect response (302) to upstream provider or consent screen

handleCallback()

Handle OAuth callback from upstream provider.
const response = await proxy.handleCallback(request);
// Returns redirect to client with authorization code
request
Request
required
Web API Request object with callback parameters
response
Response
HTTP redirect response (302) to client callback URL with authorization code

handleConsent()

Handle user consent form submission.
const response = await proxy.handleConsent(request);
// Returns redirect based on user's choice (approve/deny)
request
Request
required
Web API Request object with form data
response
Response
HTTP redirect response based on user action

exchangeAuthorizationCode()

Exchange authorization code for access token.
const tokens = await proxy.exchangeAuthorizationCode({
  grant_type: "authorization_code",
  code: "auth-code-123",
  client_id: "client-123",
  redirect_uri: "http://localhost:3000/callback",
  code_verifier: "...", // If using PKCE
});

console.log(tokens.access_token);
console.log(tokens.refresh_token);
request
TokenRequest
required
response
TokenResponse

exchangeRefreshToken()

Refresh access token using refresh token.
const tokens = await proxy.exchangeRefreshToken({
  grant_type: "refresh_token",
  refresh_token: "refresh-token-123",
  client_id: "client-123",
  scope: "openid profile", // Optional: request reduced scope
});
request
RefreshRequest
required
response
TokenResponse
New access token and optionally rotated refresh token

loadUpstreamTokens()

Load upstream tokens from a FastMCP JWT (token swap mode only).
const upstreamTokens = await proxy.loadUpstreamTokens(fastmcpJwt);

if (upstreamTokens) {
  // Use upstream access token for API calls
  const response = await fetch("https://api.example.com/user", {
    headers: {
      Authorization: `Bearer ${upstreamTokens.accessToken}`,
    },
  });
}
fastmcpToken
string
required
FastMCP JWT access token from token swap
tokens
UpstreamTokenSet | null
Upstream token set or null if invalid/expired

getAuthorizationServerMetadata()

Get OAuth Authorization Server metadata (RFC 8414).
const metadata = proxy.getAuthorizationServerMetadata();
console.log(metadata.issuer);
console.log(metadata.authorizationEndpoint);
console.log(metadata.tokenEndpoint);
metadata
object

destroy()

Stop cleanup interval and destroy resources.
proxy.destroy();

Token Swap Pattern

When enableTokenSwap: true (default), the proxy uses a secure token swap pattern:
  1. Client authorizes: Client gets authorization code from proxy
  2. Code exchange: Proxy exchanges code with upstream provider
  3. Upstream tokens stored: Proxy securely stores upstream tokens (encrypted)
  4. FastMCP JWTs issued: Proxy issues short-lived JWTs to client
  5. JWT mapping: JWTs contain JTI that maps to upstream tokens
  6. Token refresh: Client refreshes FastMCP JWT, proxy refreshes upstream tokens
Benefits:
  • Security: Upstream tokens never leave the proxy
  • Short-lived: Client tokens expire quickly (default 1 hour)
  • Auditable: All token usage tracked through proxy
  • Claims extraction: Custom claims from upstream tokens included in JWTs

Pass-through Pattern

When enableTokenSwap: false, the proxy acts as a transparent pass-through:
  1. Client authorizes: Client gets authorization code from proxy
  2. Code exchange: Proxy exchanges code with upstream provider
  3. Upstream tokens returned: Proxy returns upstream tokens directly to client
  4. Direct API access: Client uses upstream tokens to call APIs directly
Benefits:
  • Simplicity: No token mapping or storage
  • Standard OAuth: Clients use standard upstream tokens
  • Long-lived: Tokens live as long as upstream provider allows