> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leanmcp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Server & Proxy

> Build MCP-compliant OAuth authorization servers with external provider proxy support

# OAuth Server & Proxy

The `@leanmcp/auth/proxy` and `@leanmcp/auth/server` modules enable you to build OAuth 2.1 authorization servers for your MCP applications. Use them to proxy authentication to external identity providers (Google, GitHub, etc.) while issuing your own tokens.

## Features

<CardGroup cols={2}>
  <Card title="External Provider Proxy" icon="arrows-rotate">
    Authenticate users via Google, GitHub, Azure, and more
  </Card>

  <Card title="RFC 8414 Metadata" icon="file-lines">
    Standard OAuth authorization server metadata
  </Card>

  <Card title="RFC 7591 DCR" icon="plus-circle">
    Dynamic Client Registration for MCP clients
  </Card>

  <Card title="PKCE Required" icon="lock">
    Enforces PKCE per MCP security requirements
  </Card>
</CardGroup>

## Installation

```bash theme={null}
npm install @leanmcp/auth express
```

***

## Architecture Overview

```mermaid theme={null}
sequenceDiagram
    participant Client as MCP Client (Claude)
    participant Server as Your MCP Server (OAuth Proxy)
    participant IdP as External IdP (Google/GitHub)

    Client->>Server: 1. Auth Request
    Server->>IdP: 2. Redirect to IdP
    IdP-->>Server: 3. IdP Token
    Server-->>Client: 4. Your Token
```

The OAuth Proxy:

1. Receives authorization requests from MCP clients
2. Redirects users to the external identity provider
3. Exchanges the IdP's code for tokens
4. Maps external tokens/user info to your internal tokens
5. Returns your tokens to the MCP client

***

## OAuth Proxy

The `OAuthProxy` class handles the complete OAuth flow with external providers.

### Basic Setup

```typescript theme={null}
import express from 'express';
import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy';

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Configure providers
const providers = [
  googleProvider({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  githubProvider({
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  }),
];

// Create proxy
const proxy = new OAuthProxy({
  baseUrl: 'https://api.example.com/auth',
  sessionSecret: process.env.SESSION_SECRET!,
  providers,
  tokenMapper: async (externalTokens, userInfo, provider) => {
    // Map external tokens to your internal tokens
    return {
      access_token: generateMyToken(userInfo),
      token_type: 'Bearer',
      expires_in: 3600,
    };
  },
});

// Mount routes
const middleware = proxy.createMiddleware();
app.get('/auth/authorize', middleware.authorize);
app.get('/auth/callback', middleware.callback);
app.post('/auth/token', middleware.token);

app.listen(3000);
```

### Configuration

```typescript theme={null}
interface OAuthProxyConfig {
  /** Base URL for OAuth endpoints (e.g., https://api.example.com/auth) */
  baseUrl: string;
  
  /** Secret for signing session/state tokens */
  sessionSecret: string;
  
  /** Array of configured OAuth providers */
  providers: OAuthProviderConfig[];
  
  /** Function to map external tokens to your tokens */
  tokenMapper: TokenMapperFunction;
  
  /** Token TTL in seconds (default: 3600) */
  tokenTTL?: number;
}
```

### Token Mapper

The `tokenMapper` function is called after successful external authentication. Use it to create your internal tokens:

```typescript theme={null}
const proxy = new OAuthProxy({
  // ...
  tokenMapper: async (externalTokens, userInfo, provider) => {
    // externalTokens: { access_token, refresh_token, ... } from provider
    // userInfo: { sub, email, name, ... } from provider's userinfo endpoint
    // provider: { id, name, ... } the provider that authenticated
    
    // Look up or create user in your database
    let user = await db.users.findByEmail(userInfo.email);
    if (!user) {
      user = await db.users.create({
        email: userInfo.email,
        name: userInfo.name,
        provider: provider.id,
      });
    }
    
    // Generate your own token
    const token = jwt.sign(
      { sub: user.id, email: user.email },
      process.env.JWT_SECRET!,
      { expiresIn: '1h' }
    );
    
    return {
      access_token: token,
      token_type: 'Bearer',
      expires_in: 3600,
      user_id: user.id,
    };
  },
});
```

***

## Pre-configured Providers

Import ready-to-use provider configurations:

### Google

```typescript theme={null}
import { googleProvider } from '@leanmcp/auth/proxy';

googleProvider({
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  scopes: ['openid', 'profile', 'email'], // optional, these are defaults
});
```

### GitHub

```typescript theme={null}
import { githubProvider } from '@leanmcp/auth/proxy';

githubProvider({
  clientId: process.env.GITHUB_CLIENT_ID!,
  clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  scopes: ['read:user', 'user:email'], // optional
});
```

### Azure AD

```typescript theme={null}
import { azureProvider } from '@leanmcp/auth/proxy';

azureProvider({
  clientId: process.env.AZURE_CLIENT_ID!,
  clientSecret: process.env.AZURE_CLIENT_SECRET!,
  tenant: process.env.AZURE_TENANT_ID!, // or 'common' for multi-tenant
});
```

### GitLab

```typescript theme={null}
import { gitlabProvider } from '@leanmcp/auth/proxy';

gitlabProvider({
  clientId: process.env.GITLAB_CLIENT_ID!,
  clientSecret: process.env.GITLAB_CLIENT_SECRET!,
  baseUrl: 'https://gitlab.com', // or self-hosted URL
});
```

### Slack

```typescript theme={null}
import { slackProvider } from '@leanmcp/auth/proxy';

slackProvider({
  clientId: process.env.SLACK_CLIENT_ID!,
  clientSecret: process.env.SLACK_CLIENT_SECRET!,
});
```

### Discord

```typescript theme={null}
import { discordProvider } from '@leanmcp/auth/proxy';

discordProvider({
  clientId: process.env.DISCORD_CLIENT_ID!,
  clientSecret: process.env.DISCORD_CLIENT_SECRET!,
});
```

***

## Custom Providers

Use `customProvider` for any OAuth 2.0 compatible identity provider:

```typescript theme={null}
import { customProvider } from '@leanmcp/auth/proxy';

const myProvider = customProvider({
  id: 'my-idp',
  name: 'My Identity Provider',
  authorizationEndpoint: 'https://idp.example.com/oauth/authorize',
  tokenEndpoint: 'https://idp.example.com/oauth/token',
  userInfoEndpoint: 'https://idp.example.com/oauth/userinfo',
  clientId: process.env.MY_IDP_CLIENT_ID!,
  clientSecret: process.env.MY_IDP_CLIENT_SECRET!,
  scopes: ['openid', 'profile', 'email'],
  supportsPkce: true,
});
```

### Provider Configuration

```typescript theme={null}
interface OAuthProviderConfig {
  /** Unique identifier for this provider */
  id: string;
  
  /** Display name */
  name: string;
  
  /** OAuth authorization endpoint */
  authorizationEndpoint: string;
  
  /** OAuth token endpoint */
  tokenEndpoint: string;
  
  /** OpenID Connect userinfo endpoint (optional) */
  userInfoEndpoint?: string;
  
  /** Client ID */
  clientId: string;
  
  /** Client secret */
  clientSecret: string;
  
  /** Scopes to request */
  scopes?: string[];
  
  /** Whether provider supports PKCE */
  supportsPkce?: boolean;
}
```

***

## OAuth Authorization Server

For full MCP OAuth compliance, use `OAuthAuthorizationServer` which adds RFC 8414 metadata and RFC 7591 Dynamic Client Registration:

```typescript theme={null}
import express from 'express';
import { OAuthAuthorizationServer } from '@leanmcp/auth/server';
import { googleProvider } from '@leanmcp/auth/proxy';

const app = express();

const authServer = new OAuthAuthorizationServer({
  issuer: 'https://api.example.com',
  
  // Upstream provider for authentication
  upstreamProvider: googleProvider({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  
  // JWT signing secret
  signingSecret: process.env.JWT_SECRET!,
  
  // Enable Dynamic Client Registration
  enableDCR: true,
  
  // Token TTL in seconds
  tokenTTL: 3600,
  
  // Optional: Custom token mapper
  tokenMapper: async (upstreamTokens, userInfo) => {
    return {
      sub: userInfo.sub,
      email: userInfo.email,
      name: userInfo.name,
    };
  },
});

// Mount OAuth routes
app.use(authServer.createRouter());

// Serves:
// GET  /.well-known/oauth-authorization-server  (RFC 8414 metadata)
// POST /oauth/register                          (RFC 7591 DCR)
// GET  /oauth/authorize                         (Authorization endpoint)
// GET  /oauth/callback                          (Provider callback)
// POST /oauth/token                             (Token endpoint)

app.listen(3000);
```

### Server Metadata (RFC 8414)

The server automatically exposes OAuth metadata at `/.well-known/oauth-authorization-server`:

```json theme={null}
{
  "issuer": "https://api.example.com",
  "authorization_endpoint": "https://api.example.com/oauth/authorize",
  "token_endpoint": "https://api.example.com/oauth/token",
  "registration_endpoint": "https://api.example.com/oauth/register",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"]
}
```

### Dynamic Client Registration (RFC 7591)

MCP clients can register dynamically:

```bash theme={null}
curl -X POST https://api.example.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["http://localhost:3001/callback"]
  }'
```

Response:

```json theme={null}
{
  "client_id": "abc123",
  "client_secret": "secret456",
  "client_name": "My MCP Client",
  "redirect_uris": ["http://localhost:3001/callback"]
}
```

***

## MCP Auth Error Responses

Use `createAuthError` from `@leanmcp/core` to return MCP-compliant authentication errors that trigger ChatGPT's OAuth linking UI:

```typescript theme={null}
import { Tool, createAuthError } from '@leanmcp/core';

export class MyService {
  @Tool({ description: 'Get private data' })
  async getPrivateData(args: any, meta?: any) {
    const token = meta?.authorization?.token;
    
    if (!token) {
      return createAuthError('Authentication required', {
        resourceMetadataUrl: `${process.env.PUBLIC_URL}/.well-known/oauth-protected-resource`,
        error: 'invalid_token',
        errorDescription: 'No access token provided',
      });
    }
    
    // ... proceed with authenticated request
  }
}
```

The `createAuthError` function returns a response with `_meta["mcp/www_authenticate"]` that signals to MCP clients (including ChatGPT) to initiate OAuth authentication.

***

## Complete Example

Here's a complete OAuth proxy server:

```typescript theme={null}
import 'dotenv/config';
import express from 'express';
import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy';
import jwt from 'jsonwebtoken';

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const PORT = 3000;
const BASE_URL = `http://localhost:${PORT}`;

// In-memory user database (use a real database in production)
const users = new Map();

const proxy = new OAuthProxy({
  baseUrl: `${BASE_URL}/auth`,
  sessionSecret: process.env.SESSION_SECRET!,
  providers: [
    googleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    githubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
  tokenMapper: async (externalTokens, userInfo, provider) => {
    // Find or create user
    const key = `${provider.id}:${userInfo.email}`;
    let user = users.get(key);
    
    if (!user) {
      user = {
        id: crypto.randomUUID(),
        email: userInfo.email,
        name: userInfo.name,
        provider: provider.id,
        createdAt: new Date(),
      };
      users.set(key, user);
      console.log('Created user:', user.email);
    }
    
    // Generate JWT
    const token = jwt.sign(
      { sub: user.id, email: user.email, name: user.name },
      process.env.JWT_SECRET!,
      { expiresIn: '1h' }
    );
    
    return {
      access_token: token,
      token_type: 'Bearer',
      expires_in: 3600,
    };
  },
});

// OAuth routes
const middleware = proxy.createMiddleware();
app.get('/auth/authorize', middleware.authorize);
app.get('/auth/callback', middleware.callback);
app.post('/auth/token', middleware.token);

// List available providers
app.get('/auth/providers', (req, res) => {
  res.json({
    providers: [
      { id: 'google', name: 'Google' },
      { id: 'github', name: 'GitHub' },
    ],
  });
});

// Protected API endpoint
app.get('/api/me', (req, res) => {
  const auth = req.headers.authorization;
  if (!auth?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  try {
    const payload = jwt.verify(auth.slice(7), process.env.JWT_SECRET!);
    res.json({ user: payload });
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
});

app.listen(PORT, () => {
  console.log(`OAuth server running at ${BASE_URL}`);
});
```

***

## Related

* [OAuth Client](/sdk/auth-oauth-client) - Client-side OAuth with PKCE and token storage
* [Authentication Overview](/sdk/auth) - Server-side authentication with `@Authenticated` decorator
* [Auth & Payment Guide](/guides/auth-and-payment) - Complete authentication integration guide
