Remove embedded OAuth implementation and flatten directory structure

Simplified the repository to have a single OAuth pattern with separate auth and MCP servers at the top level. Preserved embedded OAuth documentation as an alternative pattern.
This commit is contained in:
Basil Hosmer 2025-10-06 21:07:18 -04:00
commit c03a9b4e77
No known key found for this signature in database
4 changed files with 1556 additions and 15846 deletions

View file

@ -1,10 +1,10 @@
# Endpoint Reference
Complete listing of all endpoints provided by each server configuration.
Complete listing of all endpoints provided by each server in the architecture.
## Embedded OAuth Server (Port 3232)
## Auth Server (Port 3001)
Single server hosting OAuth authorization, mock identity provider, and MCP resources.
Standalone OAuth 2.0 authorization server that handles authentication and token management.
### OAuth Authorization Endpoints
Provided by `mcpAuthRouter` from MCP SDK:
@ -15,62 +15,21 @@ Provided by `mcpAuthRouter` from MCP SDK:
- `POST /token` - Token exchange (authorization code → tokens) and token refresh
- `POST /revoke` - Token revocation
### Token Introspection
Custom implementation for resource server token validation:
- `POST /introspect` - Token introspection ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662))
- Called by MCP server to validate tokens
- Returns token status, scopes, expiry, user info
- Protected endpoint (not public)
### Mock Upstream Identity Provider Endpoints
Local simulation of upstream IDP (would be external in production):
- `GET /mock-upstream-idp/authorize` - Mock user authentication page
- `GET /mock-upstream-idp/callback` - IDP callback handler (returns userId)
**Note**: In production, the OAuth server would redirect to external URLs like `https://accounts.google.com` or `https://login.okta.com` instead of these local endpoints.
### MCP Resource Endpoints
#### Streamable HTTP Transport (Recommended)
- `GET /mcp` - Establish SSE stream for session
- `POST /mcp` - Initialize session or send messages
- `DELETE /mcp` - Terminate session
#### SSE Transport (Legacy)
- `GET /sse` - Establish SSE connection
- `POST /message` - Send messages to session
All MCP endpoints require `Authorization: Bearer <token>` header.
### Static Assets
- `GET /` - Splash page (HTML)
- `GET /mcp-logo.png` - MCP logo
- `GET /styles.css` - Stylesheet
---
## External OAuth - Auth Server (Port 3001)
Standalone OAuth authorization server (represents Auth0, Okta, etc. in production).
### OAuth Authorization Endpoints
Provided by `mcpAuthRouter` from MCP SDK:
- `GET /.well-known/oauth-authorization-server` - OAuth metadata discovery
- `POST /register` - Dynamic client registration
- `GET /authorize` - Authorization request
- `POST /token` - Token exchange and refresh
- `POST /revoke` - Token revocation
### Token Introspection
Custom implementation for resource server token validation:
- `POST /introspect` - Token introspection (RFC 7662)
- Called by MCP server to validate tokens
- Returns token status, scopes, expiry, user info
- Protected endpoint (not public)
### Mock Upstream Identity Provider Endpoints
Local simulation of upstream IDP:
- `GET /mock-upstream-idp/authorize` - Mock user authentication page
- `GET /mock-upstream-idp/callback` - IDP callback handler
**Note**: Commercial OAuth providers (Auth0, Okta) have their own user authentication systems. These endpoints simulate that functionality.
**Note**: In production, this would redirect to external providers like Auth0, Okta, Google, GitHub, etc. These endpoints simulate that functionality for demonstration purposes.
### Utility Endpoints
- `GET /health` - Health check (returns server status)
@ -78,9 +37,9 @@ Local simulation of upstream IDP:
---
## External OAuth - MCP Server (Port 3232)
## MCP Server (Port 3232)
Pure MCP resource server with no OAuth authorization functionality.
MCP resource server that implements the Model Context Protocol with delegated authentication.
### OAuth Metadata (Read-Only)
Provided by `mcpAuthMetadataRouter`:
@ -103,21 +62,21 @@ Provided by `mcpAuthMetadataRouter`:
All MCP endpoints require `Authorization: Bearer <token>` header. Tokens are validated by calling the auth server's `/introspect` endpoint.
### Static Assets
- `GET /` - Splash page
- `GET /` - Splash page (HTML)
- `GET /mcp-logo.png` - MCP logo
- `GET /styles.css` - Stylesheet
---
## Key Differences
## Architecture Overview
| Endpoint Type | Embedded OAuth | External Auth Server | External MCP Server |
|---------------|----------------|---------------------|---------------------|
| OAuth authorization (`/authorize`, `/token`) | ✅ Full | ✅ Full | ❌ None |
| Token introspection (`/introspect`) | ❌ Not needed | ✅ Yes | ❌ Not needed |
| OAuth metadata discovery | ✅ Yes | ✅ Yes | ✅ Read-only redirect |
| Mock IDP (`/mock-upstream-idp`) | ✅ Yes | ✅ Yes | ❌ No |
| MCP resources (`/mcp`, `/sse`) | ✅ Yes | ❌ No | ✅ Yes |
| Endpoint Type | Auth Server | MCP Server |
|---------------|-------------|------------|
| OAuth authorization (`/authorize`, `/token`) | ✅ Full implementation | ❌ None (delegates to auth server) |
| Token introspection (`/introspect`) | ✅ Provides service | ❌ Consumes service |
| OAuth metadata discovery | ✅ Authoritative | ✅ Read-only redirect |
| Mock IDP (`/mock-upstream-idp`) | ✅ Yes | ❌ No |
| MCP resources (`/mcp`, `/sse`) | ❌ No | ✅ Yes |
---
@ -146,9 +105,20 @@ All MCP endpoints require `Authorization: Bearer <token>` header. Tokens are val
---
## Authentication Flow
1. **Client discovers auth server**: GET `/.well-known/oauth-authorization-server` from MCP server
2. **Client registers**: POST to auth server's `/register` endpoint
3. **User authorizes**: Redirected to auth server's `/authorize` endpoint
4. **Token exchange**: POST to auth server's `/token` endpoint
5. **Access MCP resources**: Use bearer token with MCP server endpoints
6. **Token validation**: MCP server validates tokens via auth server's `/introspect` endpoint
---
## References
- [MCP Transport Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports)
- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)
- [OAuth 2.0 Endpoints](https://www.oauth.com/oauth2-servers/definitions/)
- [OAuth 2.0 Endpoints](https://www.oauth.com/oauth2-servers/definitions/)

View file

@ -1,10 +1,10 @@
# OAuth 2.0 + PKCE Flow Analysis
This document details the complete OAuth 2.0 authorization code flow with PKCE as implemented in this reference server, including how it differs between integrated and separate modes.
This document details the complete OAuth 2.0 authorization code flow with PKCE as implemented in this reference server with separate authorization and resource servers.
## Flow Overview
The server implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for secure authorization. Here's how each step maps to data storage, expiry, and mode-specific behavior:
The server implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for secure authorization. The architecture separates the authorization server (port 3001) from the MCP resource server (port 3232).
## 1. Client Registration
@ -19,8 +19,7 @@ Auth Server → App: {"client_id": "XYZ", "client_secret": "ABC", ...}
**Storage**:
- Redis key: `auth:client:{clientId}`
- Expiry: 30 days (long-lived app credentials)
**Mode differences**: None - identical in both modes.
- Server: Auth Server (port 3001)
---
@ -44,10 +43,7 @@ Auth Server: Saves pending authorization, shows authorization page
- Redis key: `auth:pending:{authCode}`
- Expiry: 10 minutes (short-lived temporary state)
- Data: redirect_uri, code_challenge, code_challenge_method, client_id, state
**Mode differences**:
- **Embedded OAuth**: OAuth server runs on MCP server (port 3232)
- **External OAuth**: OAuth server runs independently (port 3001)
- Server: Auth Server (port 3001)
---
@ -55,23 +51,19 @@ Auth Server: Saves pending authorization, shows authorization page
**Purpose**: Authenticate the user and obtain consent.
**Flow** (identical in both modes):
**Flow**:
```
OAuth Server → User: Shows auth page with "Continue to Authentication" button
User → OAuth Server: Clicks button
OAuth Server → Upstream IDP: Redirects to /mock-upstream-idp/authorize
Auth Server → User: Shows auth page with "Continue to Authentication" button
User → Auth Server: Clicks button
Auth Server → Upstream IDP: Redirects to /mock-upstream-idp/authorize
Upstream IDP → User: Shows user selection UI
User → Upstream IDP: Selects/creates user ID
Upstream IDP → OAuth Server: Redirects to /mock-upstream-idp/callback?userId=X
OAuth Server: Validates user, issues authorization code
OAuth Server → App: Redirects to app's redirect_uri with code
Upstream IDP → Auth Server: Redirects to /mock-upstream-idp/callback?userId=X
Auth Server: Validates user, issues authorization code
Auth Server → App: Redirects to app's redirect_uri with code
```
**Mode differences**:
- **Embedded OAuth**: OAuth Server runs on MCP server (port 3232)
- **External OAuth**: OAuth Server runs independently (port 3001)
The flow itself is identical. Both modes delegate user authentication to an upstream IDP (simulated by `/mock-upstream-idp/*` endpoints, which represent corporate SSO or social login in production).
**Note**: In production, the upstream IDP would be an external provider (Auth0, Okta, Google, GitHub, etc.). The `/mock-upstream-idp/*` endpoints simulate this for demonstration purposes.
---
@ -109,10 +101,7 @@ Auth Server → App:
- `auth:installation:{accessToken}` - Active MCP installation
- `auth:refresh:{refreshToken}` - Refresh token mapping
- Expiry: 10 minutes for exchange record, 7 days for installation
**Mode differences**: None in the exchange itself. The endpoint location differs:
- **Embedded OAuth**: `http://localhost:3232/token`
- **External OAuth**: `http://localhost:3001/token`
- Server: Auth Server (port 3001)
---
@ -127,29 +116,28 @@ App → MCP Server: POST /mcp
Mcp-Session-Id: <session_id>
{MCP request}
MCP Server: Validates token → Serves MCP resource
MCP Server → Auth Server: POST /introspect
token=<access_token>
Auth Server → MCP Server:
{
"active": true,
"userId": "...",
"exp": ...,
"aud": "http://localhost:3232"
}
MCP Server: Validates audience, serves MCP resource
```
**Token Validation - Embedded OAuth**:
```
MCP Server (same process as OAuth server):
- Reads auth:installation:{accessToken} from Redis
- Validates expiry, scopes
- Returns user info directly
```
**Token Validation Process**:
1. MCP Server receives request with bearer token
2. Calls Auth Server's `/introspect` endpoint (RFC 7662)
3. Auth Server validates token and returns metadata
4. MCP Server validates audience matches `BASE_URI`
5. Request proceeds with authenticated user context
**Token Validation - External OAuth**:
```
MCP Server:
- Calls POST {AUTH_SERVER_URL}/introspect with token
- Auth server validates and returns token info
- MCP server validates audience matches BASE_URI
- Proceeds with request
```
**Mode differences**: This is the key architectural difference:
- **Embedded OAuth**: In-process token validation (fast, direct Redis access)
- **External OAuth**: Remote token validation via HTTP introspection (adds network hop, follows RFC 7662)
This separation allows the Auth Server to be replaced with commercial providers (Auth0, Okta) while keeping the MCP server unchanged.
---
@ -179,8 +167,7 @@ Auth Server → App: {"access_token": "...", ...}
- Writes: New `auth:installation:{newAccessToken}`
- Writes: New `auth:refresh:{newRefreshToken}`
- Expiry: 7 days
**Mode differences**: None - identical flow, just different endpoint locations.
- Server: Auth Server (port 3001)
---
@ -205,7 +192,17 @@ PKCE prevents authorization code interception attacks:
This ensures only the client that initiated the flow can exchange the code, even if the code is intercepted.
**Mode differences**: None - PKCE works identically in both modes.
---
## Architecture Benefits
The separate server architecture provides:
1. **Standards Compliance**: Follows OAuth 2.0 best practices for resource/authorization server separation
2. **Flexibility**: Auth server can be replaced with Auth0, Okta, or other providers
3. **Scalability**: Auth and MCP servers can scale independently based on load
4. **Security**: Token validation via introspection endpoint (RFC 7662)
5. **Maintainability**: Clear separation of authentication and business logic
---
@ -214,4 +211,4 @@ This ensures only the client that initiated the flow can exchange the code, even
- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [RFC 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636)
- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)
- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
- [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)

269
docs/oauth-patterns.md Normal file
View file

@ -0,0 +1,269 @@
# OAuth Architecture Patterns
This document describes different OAuth 2.0 architecture patterns for MCP servers, as specified in the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization).
## Overview
Per the MCP specification, "the authorization server may be hosted with the resource server or as a separate entity." This leads to two primary patterns for implementing OAuth in MCP servers.
## Pattern 1: Separate Authorization Server (Implemented)
The current implementation uses this production-ready pattern with separate authorization and resource servers.
### Architecture
```
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ OAuth │ │ Token │ │
│ MCP Client │────────>│ Auth Server │<────────│ MCP Server │
│ │ │ (port 3001) │validate │ (port 3232) │
│ │<────────│ │ │ │
│ │ token │ Issues tokens │ │ Serves MCP │
│ │ │ │ │ resources │
│ │────────────────────────────────────> │ │
│ │ MCP requests with token │ │
└─────────────┘ └─────────────────┘
```
### Components
1. **Authorization Server** (port 3001)
- Handles OAuth 2.0 authorization flow
- Issues and manages tokens
- Provides token introspection endpoint (RFC 7662)
- Can be replaced with Auth0, Okta, Google OAuth, etc.
2. **MCP Resource Server** (port 3232)
- Serves MCP protocol resources
- Validates tokens via introspection
- Contains no OAuth authorization code
- Focuses purely on MCP functionality
### Benefits
- **Standards Compliance**: Follows OAuth 2.0 best practices
- **Flexibility**: Easy to swap auth providers
- **Scalability**: Servers scale independently
- **Security**: Clear security boundaries
- **Maintainability**: Separation of concerns
### Real-World Use Cases
- **SaaS Applications**: Using Auth0 or Okta for authentication
- **Enterprise**: Integrating with corporate SSO (SAML, LDAP)
- **Social Login**: Google, GitHub, Facebook authentication
- **Cloud Native**: AWS Cognito, Azure AD integration
### Implementation Details
The MCP server validates tokens by calling the auth server's `/introspect` endpoint:
```typescript
// In MCP server
const response = await fetch(`${AUTH_SERVER_URL}/introspect`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `token=${accessToken}`
});
const introspection = await response.json();
if (introspection.active) {
// Token is valid, extract user info
const userId = introspection.sub;
}
```
---
## Pattern 2: Embedded Authorization Server (Alternative)
An alternative pattern where the OAuth server is embedded within the MCP server. This demonstrates a self-hosted OAuth 2.1 authorization server running in the same process as the MCP server.
### Architecture
```
┌─────────────┐ ┌─────────────────────────┐
│ │ OAuth │ │
│ MCP Client │────────>│ MCP Server │
│ │ │ ┌──────────────────┐ │
│ │<────────│ │ OAuth Server │ │
│ │ token │ └──────────────────┘ │
│ │ │ ┌──────────────────┐ │
│ │────────>│ │ MCP Resources │ │
│ │ MCP │ └──────────────────┘ │
└─────────────┘ └─────────────────────────┘
```
### Upstream Delegation Pattern
Embedded OAuth often delegates user authentication to an upstream identity provider while maintaining control over token issuance:
```
MCP Client MCP+OAuth Server Upstream IDP
│ │ (Corporate SSO)
│──1. /authorize───────────────>│ │
│<───(show auth page)───────────│ │
│ │ │
│──2. Click "Continue"──────────>│ │
│<──redirect to upstream────────│ │
│ │ │
│──3. /upstream/authorize────────────────────────────────────────>│
│<──(authenticate user)──────────────────────────────────────────│
│──4. Provide credentials────────────────────────────────────────>│
│<──redirect with userId─────────────────────────────────────────│
│ │ │
│──5. /callback with userId───────>│ │
│ │──(validate userId) │
│ │──(issue MCP tokens) │
│<──redirect with auth code──────│ │
│ │ │
│──6. /token (exchange code)─────>│ │
│<──MCP access token─────────────│ │
```
### Characteristics
- **Single Server**: OAuth and MCP in one process
- **Port**: Typically runs on single port (e.g., 3232)
- **Token Validation**: In-process, direct database access
- **Deployment**: Simpler, fewer moving parts
- **Upstream Delegation**: Can delegate authentication to corporate SSO while controlling token issuance
### Benefits
- **Simplicity**: Single server to deploy and manage
- **Performance**: No network hop for token validation
- **Self-Contained**: All functionality in one codebase
- **Control**: Full control over token issuance while leveraging existing identity infrastructure
### Drawbacks
- **Coupling**: Auth and MCP logic intertwined
- **Scalability**: Can't scale auth independently
- **Flexibility**: Harder to switch auth providers
- **Updates**: Auth changes require MCP server updates
### Use Cases
- **Enterprise Deployments**: Organizations needing control over OAuth token issuance while leveraging existing corporate identity infrastructure (LDAP, Active Directory, SAML)
- **Proof of Concepts**: Quick prototypes and demonstrations
- **Small Deployments**: Internal tools with simple auth needs
- **Isolated Systems**: Air-gapped environments without external connectivity
- **Custom Auth Requirements**: Specialized authentication needs not met by standard providers
### Typical Configuration
Environment variables for embedded pattern:
```bash
BASE_URI=http://localhost:3232 # Single server URL
PORT=3232 # Single port for OAuth + MCP
REDIS_URL=redis://localhost:6379 # Session storage
UPSTREAM_IDP_URL=https://corp.sso # Optional: upstream identity provider
```
### Code Organization
Typical structure for embedded OAuth implementation:
```
src/
├── index.ts # Main entry point (MCP + OAuth + routing)
├── auth/
│ ├── provider.ts # OAuth server implementation
│ └── auth-core.ts # Token generation, PKCE utilities
├── services/
│ ├── mcp.ts # MCP protocol implementation
│ ├── auth.ts # Auth service integration
│ ├── redis-auth.ts # Redis auth operations
│ └── redisTransport.ts # Redis-backed transport
├── handlers/
│ ├── oauth.ts # OAuth endpoints (/authorize, /token)
│ ├── mcp-shttp.ts # Streamable HTTP handler
│ ├── mcp-sse.ts # SSE handler
│ └── upstream-idp.ts # Upstream IDP integration
└── utils/
└── logger.ts # Structured logging
```
---
## Comparison
| Aspect | Separate Servers | Embedded Server |
|--------|------------------|-----------------|
| **Architecture** | 2+ servers | 1 server |
| **OAuth endpoints** | On auth server | On MCP server |
| **Token validation** | Remote (introspection) | In-process |
| **Deployment** | More complex | Simpler |
| **Scalability** | Independent scaling | Coupled scaling |
| **Provider integration** | Easy (standard APIs) | Difficult |
| **Code organization** | Clear separation | Mixed concerns |
| **Network hops** | Additional for validation | None for validation |
| **Upstream IDP support** | Via auth server | Direct integration |
| **Production readiness** | ✅ Recommended | ⚠️ Limited use cases |
---
## Migration Path
### From Embedded to Separate
If starting with embedded OAuth, migration to separate servers involves:
1. **Extract auth code**: Move OAuth handlers to separate service
2. **Implement introspection**: Add RFC 7662 endpoint
3. **Update MCP server**: Replace in-process validation with introspection calls
4. **Update configuration**: Point MCP server to auth server URL
5. **Migrate upstream IDP**: Move delegation logic to auth server
6. **Test thoroughly**: Ensure token flow works correctly
### From Separate to Commercial Provider
Replacing the demo auth server with a commercial provider:
1. **Configure provider**: Set up OAuth app in Auth0/Okta
2. **Update metadata URL**: Point to provider's discovery endpoint
3. **Configure introspection**: Set up token validation
4. **Update redirect URIs**: Configure allowed callbacks
5. **Migrate users**: Import existing users if needed
6. **Test integration**: Verify full OAuth flow
---
## Best Practices
1. **Use separate servers** for production deployments
2. **Implement token caching** to reduce introspection calls
3. **Use PKCE** for all OAuth flows (prevents code interception)
4. **Validate token audience** to prevent token substitution
5. **Implement proper session management** with Redis or similar
6. **Use HTTPS** in production for all endpoints
7. **Monitor token expiration** and implement refresh logic
8. **Log authentication events** for security auditing
9. **Consider upstream delegation** for enterprise deployments
10. **Document auth flow** for your specific implementation
---
## Testing Considerations
When implementing either pattern, ensure comprehensive testing:
- **Unit tests**: Test OAuth flows, token validation, session management
- **Integration tests**: Test auth server and MCP server interaction
- **E2E tests**: Test complete user flows from authorization to resource access
- **Security tests**: Test PKCE validation, token expiration, session isolation
- **Performance tests**: Test token validation caching, concurrent sessions
---
## References
- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)
- [RFC 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636)
- [OAuth 2.0 Security Best Practices](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics)
- [OAuth 2.1 Draft](https://oauth.net/2.1/) - Modern security requirements
- [The OAuth 2.0 Authorization Framework in Practice](https://www.oauth.com/)

View file

@ -23,11 +23,11 @@ User ID sources:
When the MCP server validates an access token:
**Embedded OAuth**: Reads `McpInstallation` from Redis, extracts `installation.userId`
1. MCP server calls the auth server's `/introspect` endpoint with the token
2. Auth server validates the token and returns user information including the `sub` field (user ID)
3. MCP server populates `AuthInfo.extra.userId` with this user identifier
**External OAuth**: Calls auth server's `/introspect` endpoint, receives `sub` field
Both result in `AuthInfo.extra.userId` being populated.
This separation allows the auth server to be replaced with any OAuth provider that supports token introspection.
### Session Creation