- Merge auth-server and mcp-server into unified architecture under src/modules/ - Add configurable auth modes: internal (single process) and external (separate processes) - Update example scripts to support OAuth discovery and both auth modes - Consolidate OAuth documentation into comprehensive implementation guide - Add configuration options for development vs production deployments - Support both Redis-backed and in-memory session storage
501 lines
No EOL
16 KiB
Markdown
501 lines
No EOL
16 KiB
Markdown
# OAuth Implementation Guide
|
|
|
|
This comprehensive guide covers the OAuth 2.0 + PKCE implementation in the MCP Feature Reference Server, including architecture patterns, detailed flow analysis, and integration guidance.
|
|
|
|
**Quick Start**: If you're new to this codebase, start with [Implementation Modes](#implementation-modes) to understand the difference between internal (development) and external (production) auth modes.
|
|
|
|
## Table of Contents
|
|
- [Architecture Overview](#architecture-overview)
|
|
- [Implementation Modes](#implementation-modes)
|
|
- [OAuth Flow Details](#oauth-flow-details)
|
|
- [Commercial Provider Integration](#commercial-provider-integration)
|
|
- [Error Handling](#error-handling)
|
|
- [Testing & Troubleshooting](#testing--troubleshooting)
|
|
- [Best Practices](#best-practices)
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
This implementation uses the **separate authorization server pattern** as recommended by the [MCP Authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization). The authorization server is architecturally separate from the MCP resource server, ensuring clean separation of concerns and enabling integration with commercial OAuth providers.
|
|
|
|
### Key Principles
|
|
|
|
1. **Separation of Concerns**: OAuth logic is isolated from MCP functionality
|
|
2. **Token-Based Security**: All MCP requests require valid bearer tokens
|
|
3. **PKCE Protection**: Prevents authorization code interception attacks
|
|
4. **Flexible Deployment**: Supports both development and production configurations
|
|
|
|
---
|
|
|
|
## Implementation Modes
|
|
|
|
The server supports two modes while maintaining the same architectural pattern:
|
|
|
|
### Internal Mode (Development)
|
|
|
|
In internal mode, OAuth endpoints run in the same process as the MCP server for convenience during development and exploration.
|
|
|
|
**Configuration:**
|
|
```bash
|
|
AUTH_MODE=internal # or leave unset (default)
|
|
# Server runs on port 3232
|
|
# OAuth and MCP endpoints share the same port
|
|
```
|
|
|
|
**Architecture:**
|
|
```
|
|
┌─────────────┐ ┌───────────────────────────────────────┐
|
|
│ │ OAuth │ Single Process (port 3232) │
|
|
│ MCP Client │────────>│ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ │ │ Auth Module │ │ MCP Module │ │
|
|
│ │<────────│ │ │ │ │ │
|
|
│ │ token │ │Issues tokens │────│ Serves MCP │ │
|
|
│ │ │ └──────────────┘ └──────────────┘ │
|
|
│ │────────>│ MCP requests with token │
|
|
└─────────────┘ └───────────────────────────────────────┘
|
|
```
|
|
|
|
### External Mode (Production)
|
|
|
|
In external mode, the authorization server runs as a separate process, following production best practices.
|
|
|
|
**Configuration:**
|
|
```bash
|
|
AUTH_MODE=external
|
|
AUTH_SERVER_URL=http://localhost:3001 # or commercial provider URL
|
|
# Auth server on port 3001
|
|
# MCP server on port 3232
|
|
```
|
|
|
|
**Architecture:**
|
|
```
|
|
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ │ OAuth │ │ Token │ │
|
|
│ MCP Client │────────>│ Auth Server │<────────│ MCP Server │
|
|
│ │ │ (port 3001) │validate │ (port 3232) │
|
|
│ │<────────│ │ │ │
|
|
│ │ token │ Issues tokens │ │ Serves MCP │
|
|
│ │ └──────────────────┘ │ resources │
|
|
│ │────────────────────────────────────> │ │
|
|
│ │ MCP requests with token │ │
|
|
└─────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### Mode Comparison
|
|
|
|
| Aspect | Internal Mode | External Mode |
|
|
|--------|--------------|---------------|
|
|
| **Process Architecture** | Single process | Multiple processes |
|
|
| **Port Usage** | One port (3232) | Two ports (3001 + 3232) |
|
|
| **OAuth Endpoints** | Same port as MCP | Different port/server |
|
|
| **Token Validation** | In-process call | HTTP introspection |
|
|
| **Production Ready** | Development only | ✅ Production recommended |
|
|
| **Commercial Auth Providers** | Not applicable | ✅ Supported |
|
|
|
|
---
|
|
|
|
## OAuth Flow Details
|
|
|
|
The implementation follows OAuth 2.0 with PKCE (RFC 7636) for secure authorization.
|
|
|
|
### 1. Client Registration
|
|
|
|
**Purpose**: Register the application with the OAuth server (one-time setup)
|
|
|
|
```http
|
|
POST /register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"client_name": "My MCP Client",
|
|
"redirect_uris": ["http://localhost:3000/callback"]
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"client_id": "abc123",
|
|
"client_secret": "secret456",
|
|
"client_id_issued_at": 1234567890,
|
|
"client_secret_expires_at": 0
|
|
}
|
|
```
|
|
|
|
**Storage**: Redis key `auth:client:{clientId}` (30-day expiry)
|
|
|
|
### 2. Authorization Request
|
|
|
|
**Purpose**: Initiate OAuth flow with PKCE
|
|
|
|
```http
|
|
GET /authorize?
|
|
client_id=abc123&
|
|
redirect_uri=http://localhost:3000/callback&
|
|
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
|
|
code_challenge_method=S256&
|
|
state=xyz789
|
|
```
|
|
|
|
**PKCE Security**:
|
|
- Client generates random `code_verifier` (43-128 characters)
|
|
- Sends SHA256 hash as `code_challenge`
|
|
- Must provide original verifier during token exchange
|
|
|
|
**Storage**: Redis key `auth:pending:{authCode}` (10-minute expiry)
|
|
|
|
### 3. User Authentication
|
|
|
|
The auth server authenticates the user and obtains consent:
|
|
|
|
1. Shows authorization page
|
|
2. User authenticates (via upstream IDP in production)
|
|
3. Issues authorization code
|
|
4. Redirects to client's `redirect_uri` with code
|
|
|
|
### 4. Token Exchange
|
|
|
|
**Purpose**: Exchange authorization code for tokens
|
|
|
|
```http
|
|
POST /token
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
grant_type=authorization_code&
|
|
client_id=abc123&
|
|
client_secret=secret456&
|
|
code=auth_code_here&
|
|
redirect_uri=http://localhost:3000/callback&
|
|
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
|
```
|
|
|
|
**PKCE Validation**:
|
|
```javascript
|
|
// Server verifies: SHA256(code_verifier) === stored code_challenge
|
|
const challenge = base64url(sha256(code_verifier));
|
|
if (challenge !== stored_code_challenge) {
|
|
throw new Error('Invalid PKCE verifier');
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGc...",
|
|
"refresh_token": "refresh_xyz",
|
|
"token_type": "Bearer",
|
|
"expires_in": 604800
|
|
}
|
|
```
|
|
|
|
### 5. Using Access Tokens
|
|
|
|
**Purpose**: Access MCP resources with bearer token
|
|
|
|
```http
|
|
POST /mcp
|
|
Authorization: Bearer eyJhbGc...
|
|
Mcp-Session-Id: session-123
|
|
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/list",
|
|
"id": 1
|
|
}
|
|
```
|
|
|
|
**Token Validation Process**:
|
|
|
|
1. MCP server extracts bearer token
|
|
2. Calls auth server's `/introspect` endpoint
|
|
3. Validates token is active and not expired
|
|
4. Extracts user ID from `sub` claim
|
|
5. Processes MCP request with user context
|
|
|
|
### 6. Token Refresh
|
|
|
|
**Purpose**: Obtain new access token when current expires
|
|
|
|
```http
|
|
POST /token
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
grant_type=refresh_token&
|
|
refresh_token=refresh_xyz&
|
|
client_id=abc123&
|
|
client_secret=secret456
|
|
```
|
|
|
|
**Response**: New access token (and optionally new refresh token)
|
|
|
|
---
|
|
|
|
## Commercial Provider Integration
|
|
|
|
The demo auth server should be replaced with a commercial OAuth provider in production.
|
|
|
|
### Supported Providers
|
|
|
|
- **Auth0** - Full OAuth 2.0 + OIDC support
|
|
- **Okta** - Enterprise identity management
|
|
- **Azure AD/Microsoft Entra** - Microsoft identity platform
|
|
- **AWS Cognito** - AWS managed authentication
|
|
- **Google Identity Platform** - Google OAuth
|
|
- **GitHub OAuth** - Developer-friendly OAuth
|
|
- Any RFC 7662-compliant OAuth provider
|
|
|
|
### Integration Steps
|
|
|
|
#### 1. Configure the Provider
|
|
|
|
In your OAuth provider's dashboard:
|
|
- Create new OAuth application/client
|
|
- Set redirect URIs (e.g., `http://localhost:3000/callback`)
|
|
- Enable token introspection endpoint (if required)
|
|
- Note client ID and secret
|
|
|
|
#### 2. Update Server Configuration
|
|
|
|
```bash
|
|
# .env file
|
|
AUTH_MODE=external
|
|
AUTH_SERVER_URL=https://your-tenant.auth0.com
|
|
# For Okta: https://your-domain.okta.com
|
|
# For Azure: https://login.microsoftonline.com/your-tenant
|
|
```
|
|
|
|
#### 3. Adjust Token Introspection (if needed)
|
|
|
|
Most providers follow RFC 7662, but some require authentication. Edit `src/interfaces/auth-validator.ts`:
|
|
|
|
```typescript
|
|
// In ExternalTokenValidator.introspect() method
|
|
const response = await fetch(`${this.authServerUrl}/oauth/introspect`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
// Some providers require authentication
|
|
'Authorization': `Basic ${Buffer.from('client_id:client_secret').toString('base64')}`
|
|
},
|
|
body: `token=${encodeURIComponent(token)}`
|
|
});
|
|
```
|
|
|
|
#### 4. Provider-Specific Examples
|
|
|
|
**Auth0:**
|
|
```bash
|
|
AUTH_SERVER_URL=https://your-tenant.auth0.com
|
|
# Introspection endpoint: /oauth/token_info
|
|
```
|
|
|
|
**Okta:**
|
|
```bash
|
|
AUTH_SERVER_URL=https://your-domain.okta.com/oauth2/default
|
|
# Introspection endpoint: /v1/introspect
|
|
```
|
|
|
|
**Azure AD:**
|
|
```bash
|
|
AUTH_SERVER_URL=https://login.microsoftonline.com/your-tenant-id
|
|
# May require additional configuration for introspection
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Common OAuth Errors
|
|
|
|
| Error | Cause | Solution |
|
|
|-------|-------|----------|
|
|
| `invalid_client` | Wrong client credentials | Verify client_id and client_secret |
|
|
| `invalid_grant` | Expired/invalid auth code | Ensure code is used within 10 minutes |
|
|
| `invalid_request` | Missing required parameters | Check all OAuth parameters are provided |
|
|
| `unauthorized_client` | Client not authorized for grant type | Verify client registration settings |
|
|
| `invalid_token` | Token expired or revoked | Refresh token or re-authenticate |
|
|
|
|
### Token Validation Errors
|
|
|
|
```typescript
|
|
// Token expired
|
|
if (result.exp && result.exp < Date.now() / 1000) {
|
|
throw new InvalidTokenError('Token has expired');
|
|
}
|
|
|
|
// Token not active
|
|
if (!result.active) {
|
|
throw new InvalidTokenError('Token is not active');
|
|
}
|
|
|
|
// Wrong audience
|
|
if (result.aud !== expectedAudience) {
|
|
throw new InvalidTokenError('Token audience mismatch');
|
|
}
|
|
```
|
|
|
|
### Error Response Format
|
|
|
|
```json
|
|
{
|
|
"error": "invalid_token",
|
|
"error_description": "The access token expired",
|
|
"error_uri": "https://tools.ietf.org/html/rfc6750#section-3.1"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing & Troubleshooting
|
|
|
|
### Testing OAuth Flows
|
|
|
|
```bash
|
|
# Test internal mode
|
|
npm run dev:internal
|
|
npx @modelcontextprotocol/inspector
|
|
# Connect to http://localhost:3232/mcp
|
|
|
|
# Test external mode
|
|
npm run dev:external
|
|
npx @modelcontextprotocol/inspector
|
|
# Connect to http://localhost:3232/mcp
|
|
|
|
# Run automated tests
|
|
npm test -- --testNamePattern="OAuth"
|
|
npm run test:e2e
|
|
```
|
|
|
|
### Common Issues and Solutions
|
|
|
|
**Issue: "Cannot connect to auth server"**
|
|
- Check `AUTH_MODE` and `AUTH_SERVER_URL` in `.env`
|
|
- Verify auth server is running (external mode)
|
|
- Check network connectivity to auth server
|
|
|
|
**Issue: "Token validation failed"**
|
|
- Verify tokens haven't expired (7-day default)
|
|
- Check Redis connection if using Redis storage
|
|
- Ensure auth server introspection endpoint is accessible
|
|
|
|
**Issue: "PKCE validation failed"**
|
|
- Ensure code_verifier is 43-128 characters
|
|
- Verify SHA256 hashing is correct
|
|
- Check code_challenge_method is 'S256'
|
|
|
|
**Issue: "Session not found"**
|
|
- Verify Redis is running if configured
|
|
- Check session hasn't expired
|
|
- Ensure same user is accessing the session
|
|
|
|
### Debug Logging
|
|
|
|
Enable detailed logging for troubleshooting:
|
|
|
|
```bash
|
|
# Enable debug logs
|
|
DEBUG=* npm run dev:internal
|
|
|
|
# Check auth flow metadata
|
|
curl -v http://localhost:3232/.well-known/oauth-authorization-server
|
|
|
|
# Test introspection (external mode only, on auth server)
|
|
# Note: In internal mode, introspection is handled internally
|
|
curl -X POST http://localhost:3001/introspect \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "token=YOUR_TOKEN"
|
|
```
|
|
|
|
### Monitoring Token Usage
|
|
|
|
```bash
|
|
# Monitor Redis for token operations (when Redis is configured)
|
|
redis-cli MONITOR | grep auth:
|
|
|
|
# Check active sessions
|
|
redis-cli KEYS "auth:installation:*"
|
|
|
|
# Check refresh tokens
|
|
redis-cli KEYS "auth:refresh:*"
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### Security
|
|
|
|
1. **Always use HTTPS in production** - Prevents token interception
|
|
2. **Implement PKCE for all flows** - Required by OAuth 2.1
|
|
3. **Validate token audience** - Prevents token substitution attacks
|
|
4. **Use short token expiries** - Limits exposure window
|
|
5. **Rotate refresh tokens** - Issue new refresh token on use
|
|
6. **Log authentication events** - For security auditing
|
|
7. **Rate limit auth endpoints** - Prevent brute force attacks
|
|
|
|
### Performance
|
|
|
|
1. **Cache introspection results** - Reduce auth server load
|
|
2. **Use connection pooling** - For Redis and HTTP connections
|
|
3. **Implement token refresh ahead of expiry** - Prevent interruptions
|
|
4. **Batch token validations** - When processing multiple requests
|
|
5. **Monitor auth server latency** - Set appropriate timeouts
|
|
|
|
### Development
|
|
|
|
1. **Use internal mode for development** - Faster iteration
|
|
2. **Test with external mode before production** - Catch integration issues
|
|
3. **Implement comprehensive error handling** - Better debugging
|
|
4. **Document your OAuth configuration** - For team members
|
|
5. **Use environment variables** - Never hardcode credentials
|
|
|
|
### Production Deployment
|
|
|
|
1. **Use commercial OAuth provider** - Better security and reliability
|
|
2. **Enable Redis for sessions** - Support multiple instances
|
|
3. **Implement health checks** - Monitor auth availability
|
|
4. **Set up monitoring and alerting** - Track auth failures
|
|
5. **Plan for token migration** - When changing providers
|
|
6. **Document disaster recovery** - Auth server failure procedures
|
|
|
|
---
|
|
|
|
## Data Lifecycle
|
|
|
|
### Storage Hierarchy
|
|
|
|
When Redis is configured, the following data is stored with automatic expiry:
|
|
|
|
| Data Type | Redis Key Pattern | Default Expiry | Purpose |
|
|
|-----------|------------------|----------------|---------|
|
|
| OAuth flow state | `auth:pending:{code}` | 10 minutes | Temporary auth state |
|
|
| Token exchange | `auth:exch:{code}` | 10 minutes | Prevent replay attacks |
|
|
| User sessions | `auth:installation:{token}` | 7 days | Active sessions |
|
|
| Refresh tokens | `auth:refresh:{token}` | 7 days | Token refresh |
|
|
| Client credentials | `auth:client:{id}` | 30 days | App registration |
|
|
|
|
**Note**: When Redis is not configured (in-memory storage), all data is lost on server restart.
|
|
|
|
### Cleanup
|
|
|
|
Expired data is automatically cleaned up by Redis TTL. For manual cleanup:
|
|
|
|
```bash
|
|
# Remove all auth data (CAUTION: will log out all users)
|
|
redis-cli --scan --pattern "auth:*" | xargs redis-cli DEL
|
|
|
|
# Remove specific user session
|
|
redis-cli DEL "auth:installation:ACCESS_TOKEN"
|
|
```
|
|
|
|
---
|
|
|
|
## 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 7636: PKCE](https://datatracker.ietf.org/doc/html/rfc7636)
|
|
- [RFC 7662: Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)
|
|
- [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 |