Skip to main content

Architecture Overview

The OAuth implementation uses:
  • Client-side state machine for flow orchestration
  • CORS proxy server for metadata discovery
  • localStorage for persistence
  • MCP SDK for OAuth operations

Complete OAuth Flow

The following diagram shows the complete OAuth flow from user initiation through token exchange:

State Machine Transitions

The OAuth flow is managed by a state machine with 6 distinct states:

Data Persistence (localStorage)

All OAuth data is persisted in the browser’s localStorage with server-specific keys:

CORS Proxy Architecture

To bypass CORS restrictions when fetching OAuth metadata, we use a proxy server:

Key Components

1. MCPOAuthProvider

Location: client/src/lib/mcp-oauth.ts:72 Implements the OAuthClientProvider interface from the MCP SDK:
// Get/create client info with custom credentials
clientInformation()

// Store client info to localStorage
saveClientInformation(clientInfo)

// Retrieve stored tokens
tokens()

// Store tokens to localStorage
saveTokens(tokens)

// Redirect user to auth server
redirectToAuthorization(url)

// Retrieve PKCE verifier
codeVerifier()

// Store PKCE verifier
saveCodeVerifier(verifier)

2. OAuth State Machine

Location: client/src/lib/oauth-state-machine.ts:353 Manages the step-by-step OAuth flow:
  • Each step has canTransition and execute methods
  • Handles errors and retries automatically
  • Updates UI state via callbacks
  • Validates preconditions before transitions

3. CORS Proxy

Location: server/routes/mcp/oauth.ts:10 Proxies OAuth metadata requests to bypass CORS:
  • Enforces HTTPS for security
  • Adds proper CORS headers
  • Validates URL format
  • Returns OAuth metadata JSON

4. Fetch Interceptor

Location: client/src/lib/mcp-oauth.ts:17 Intercepts and proxies OAuth metadata requests:
function createOAuthFetchInterceptor() {
  return async function interceptedFetch(input, init) {
    const url = extractUrl(input);

    // Check if this is an OAuth metadata request
    if (url.includes('/.well-known/oauth-authorization-server')) {
      // Proxy through our server to avoid CORS
      const proxyUrl = `/api/mcp/oauth/metadata?url=${encodeURIComponent(url)}`;
      return await originalFetch(proxyUrl, init);
    }

    // For all other requests, use original fetch
    return await originalFetch(input, init);
  };
}

OAuth Flow States

From client/src/lib/oauth-flow-types.ts:10-16:
StateDescription
metadata_discoveryDiscover OAuth & resource metadata from server
client_registrationRegister client (DCR) or use pre-configured
authorization_redirectGenerate auth URL with PKCE
authorization_codeWait for user authorization
token_requestExchange code for tokens
completeOAuth flow finished

Security Features

PKCE

Code verifier stored at oauth-state-machine.ts:248 for secure authorization without client secrets

CSRF Protection

Random state parameter generated at oauth-state-machine.ts:231-236

HTTPS Enforcement

Proxy validates HTTPS at oauth.ts:22

Scope Validation

Uses advertised scopes from metadata at oauth-state-machine.ts:222-229

Error Handling

Each state handles errors gracefully:
  • Stores error in latestError state field
  • Updates statusMessage with user-friendly error
  • Provides helpful error messages for common issues:
    • invalid_client - Client ID verification failed
    • unauthorized_client - Client not authorized for this server
    • invalid_grant - Authorization code expired or invalid
  • Allows retry from current step without restarting flow

Token Management

1

Storage

Tokens stored in localStorage with server-specific keys
2

Refresh

Automatic token refresh using refresh_token at mcp-oauth.ts:464
3

Usage

Tokens added as Bearer token in Authorization header at mcp-oauth.ts:577-580
4

Cleanup

clearOAuthData() removes all OAuth-related localStorage entries

Usage Example

// Initiate OAuth flow
const result = await initiateOAuth({
  serverName: 'my-mcp-server',
  serverUrl: 'https://mcp.example.com',
  scopes: ['read', 'write'],
  clientId: 'optional-custom-client-id', // Optional
  clientSecret: 'optional-client-secret' // Optional
});

if (result.success && result.serverConfig) {
// Use server config for authenticated requests
// serverConfig includes Authorization header with Bearer token
}

File References

ComponentLocation
OAuth Flow Typesclient/src/lib/oauth-flow-types.ts
State Machineclient/src/lib/oauth-state-machine.ts
OAuth Providerclient/src/lib/mcp-oauth.ts
CORS Proxyserver/routes/mcp/oauth.ts
The OAuth implementation follows the MCP specification and uses PKCE for enhanced security. All sensitive data is stored in localStorage and never sent to unauthorized servers.
I