Skip to content

Commit 76f86da

Browse files
committed
feat: enhance MCP OAuth Gateway with full compliance and security features
- Updated architecture documentation to reflect full implementation of resource parameter support and service-specific audience binding per RFC 8707. - Added advanced security features including Origin header validation, MCP-Protocol-Version validation, and localhost binding warnings. - Implemented middleware for origin validation and MCP protocol version checking in the gateway. - Enhanced metadata provider to generate service-specific canonical URIs. - Updated tests to cover new middleware functionality and ensure compliance with security requirements.
1 parent 2c2adf2 commit 76f86da

File tree

11 files changed

+698
-88
lines changed

11 files changed

+698
-88
lines changed

ARCHITECTURE.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -669,12 +669,15 @@ x-user-provider: google
669669

670670
#### Advanced Security Features
671671
- PKCE code challenge validation (S256 required) ✅
672-
- JWT audience validation with resource binding ✅
672+
- JWT audience validation with service-specific resource binding ✅
673673
- Comprehensive redirect URI validation ✅
674674
- State parameter CSRF protection with expiration ✅
675675
- Bearer token authentication with timeout handling ✅
676676
- Client deduplication and credential security ✅
677677
- Single provider constraint enforcement ✅
678+
- Origin header validation for DNS rebinding protection ✅
679+
- MCP-Protocol-Version validation and enforcement ✅
680+
- Localhost binding warnings for development security ✅
678681

679682
#### Production-Ready MCP Integration
680683
- HTTP proxy to backend MCP services with connection pooling ✅
@@ -693,12 +696,13 @@ x-user-provider: google
693696
- Configuration testing (YAML loading, environment variables, validation) ✅
694697
- Error handling and edge case testing with mocked scenarios ✅
695698

696-
### ⚠️ Partial Implementation
699+
### ✅ Full Implementation
697700

698701
#### Resource Parameter Support
699-
- Resource parameter accepted in requests ⚠️
700-
- Currently uses gateway issuer as audience instead of service-specific resources
701-
- MCP clients work with this approach but not fully RFC 8707 compliant
702+
- Resource parameter accepted and properly implemented per RFC 8707 ✅
703+
- Service-specific canonical URIs used as audience (e.g., `https://gateway.com/calculator/mcp`) ✅
704+
- Proper token audience binding prevents cross-service token reuse ✅
705+
- MCP clients get tokens bound to specific services per specification ✅
702706

703707
### ❌ Current Limitations
704708

@@ -874,20 +878,21 @@ The gateway implements the MCP authorization specification based on OAuth 2.1 st
874878
- Redirect URI validation
875879
- State parameter for CSRF protection
876880

877-
#### ⚠️ Partial Compliance
881+
#### ✅ Full Compliance
878882

879883
**Resource Parameter Implementation**
880884
- Accepts resource parameter in authorization and token requests ✅
881-
- **Issue**: Currently uses gateway issuer as audience instead of service-specific canonical URIs
882-
- **MCP Requirement**: Should use canonical URIs like `https://mcp.example.com/{service-id}/mcp`
883-
**Single Provider Constraint**: Resource parameter binding requires all services to use the same OAuth provider per gateway instance
885+
- Implements service-specific canonical URIs per RFC 8707 ✅
886+
- Uses canonical URIs like `https://gateway.example.com/{service-id}/mcp`
887+
- **Single Provider Constraint**: Resource parameter binding requires all services to use the same OAuth provider per gateway instance (architectural design choice)
884888

885889
#### 📝 Implementation Notes
886890

887891
**Resource Parameter Handling**
888-
- Uses gateway issuer as token audience for simplicity
889-
- Works effectively with MCP clients in development scenarios
890-
- Resource parameter accepted and stored for future enhancement
892+
- Implements service-specific canonical URIs per RFC 8707 (e.g., `https://gateway.com/calculator/mcp`)
893+
- Tokens are bound to specific services preventing cross-service reuse
894+
- Full compliance with MCP Authorization specification requirements
895+
- Proper audience validation ensures security isolation between services
891896

892897
### MCP Transport Specification (2025-06-18)
893898

CLAUDE.md

Lines changed: 167 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ This file provides guidance to Claude Code when working with the MCP OAuth Gatew
44

55
## Project Overview
66

7-
The **MCP OAuth Gateway** is a work-in-progress OAuth 2.1 authorization server that provides transparent authentication and authorization for Model Context Protocol (MCP) services. It acts as a secure proxy that handles all OAuth complexity, allowing users to simply access `https://gateway.example.com/<service-id>/mcp` and have authentication handled automatically.
7+
The **MCP OAuth Gateway** is a work-in-progress OAuth 2.1 authorization server that provides transparent authentication and authorization for Model Context Protocol (MCP) services. It acts as a secure proxy that handles all OAuth complexity, allowing users to simply access `https://gateway.example.com/{service-id}/mcp` and have authentication handled automatically.
8+
9+
**Key Features:**
10+
- **Service-Specific Token Binding**: Implements RFC 8707 resource parameters with canonical URIs
11+
- **MCP Protocol Compliance**: Full support for MCP Authorization specification (2025-06-18)
12+
- **Security Middleware Stack**: DNS rebinding protection and protocol validation
13+
- **Single Provider Architecture**: Simplified OAuth configuration with consistent authentication
814

915
## Codebase Structure
1016

@@ -44,15 +50,23 @@ src/
4450
- FastAPI app with OAuth 2.1 and MCP service endpoints
4551
- Command-line interface with config file support
4652
- Health checks and service discovery endpoints
53+
- Security middleware stack (Origin validation, MCP protocol validation)
4754
- CORS middleware and security headers
4855
- Lifespan management for async resources
4956

50-
**Key functions:**
57+
**Key classes and functions:**
5158
- `McpGateway` class - Main gateway orchestrator with all OAuth and MCP functionality
59+
- `OriginValidationMiddleware` - DNS rebinding protection with localhost enforcement
60+
- `MCPProtocolVersionMiddleware` - MCP protocol version validation and compatibility
5261
- `create_app(config: Config) -> FastAPI` - Application factory
5362
- `main()` - CLI entry point with argument parsing
5463
- `_determine_provider_for_resource()` - Single provider constraint enforcement
5564

65+
**Security Middleware Stack:**
66+
- **Origin Validation**: Protects against DNS rebinding attacks with configurable localhost enforcement
67+
- **MCP Protocol Validation**: Validates MCP-Protocol-Version headers for protocol compliance
68+
- **CORS Protection**: Standard CORS middleware with configurable policies
69+
5670
### 2. OAuth Authentication System (`auth/`)
5771

5872
#### Models (`models.py`)
@@ -163,6 +177,60 @@ OAuth metadata endpoints per RFCs:
163177
- **Authorization Server Metadata** (RFC 8414) at `/.well-known/oauth-authorization-server`
164178
- **Protected Resource Metadata** (RFC 9728) at `/.well-known/oauth-protected-resource`
165179
- **Service discovery** endpoints for MCP services
180+
- **Service-specific canonical URIs** per RFC 8707 for proper audience binding
181+
182+
**Key methods:**
183+
- `get_service_canonical_uri(service_id: str)` - Generates canonical URI for service-specific tokens
184+
- `get_all_service_canonical_uris()` - Returns mapping of all service canonical URIs
185+
- `get_authorization_server_metadata()` - RFC 8414 compliant metadata
186+
- `get_protected_resource_metadata(service_id)` - RFC 9728 compliant resource metadata
187+
188+
### 6. Security Middleware (`gateway.py`)
189+
Comprehensive security middleware stack protecting against common web attacks:
190+
191+
#### Origin Validation Middleware
192+
Protects against DNS rebinding attacks with environment-aware configuration:
193+
194+
**Features:**
195+
- **DNS Rebinding Protection**: Validates Origin headers against allowed origins
196+
- **Localhost Enforcement**: Development-friendly localhost access with production security
197+
- **Environment Awareness**: Different security levels for debug vs production modes
198+
199+
**Configuration:**
200+
```python
201+
OriginValidationMiddleware(
202+
allowed_origins=["https://trusted.example.com", "https://app.company.com"],
203+
enforce_localhost=not config.debug # True in production, False in development
204+
)
205+
```
206+
207+
**Security Behavior:**
208+
- **Production Mode** (`debug=False`, `enforce_localhost=True`):
209+
- ✅ Allowed: Origins in explicit allow list
210+
- ✅ Allowed: Localhost origins (`http://localhost:*`, `https://127.0.0.1:*`)
211+
- ❌ Blocked: All other origins
212+
- **Development Mode** (`debug=True`, `enforce_localhost=False`):
213+
- ✅ Allowed: Origins in explicit allow list
214+
- ✅ Allowed: Localhost origins
215+
- ✅ Allowed: Any other origin (permissive for development)
216+
217+
#### MCP Protocol Version Middleware
218+
Validates MCP protocol compliance and version compatibility:
219+
220+
**Features:**
221+
- **Protocol Version Validation**: Ensures clients use supported MCP protocol versions
222+
- **Backward Compatibility**: Supports multiple MCP specification versions
223+
- **Path-Specific Validation**: Only validates requests to MCP endpoints (`/mcp` paths)
224+
225+
**Supported Versions:**
226+
- `2025-06-18` (Current MCP specification)
227+
- `2025-03-26` (Backward compatibility)
228+
229+
**Validation Logic:**
230+
- Validates `MCP-Protocol-Version` header on requests to `/{service-id}/mcp` endpoints
231+
- Returns 400 error for unsupported versions with helpful error messages
232+
- Allows requests without version header (backend should handle gracefully)
233+
- Bypasses validation for non-MCP endpoints
166234

167235
## Development Guidelines
168236

@@ -200,6 +268,40 @@ mcp_services:
200268

201269
3. **Test the provider** with your MCP services
202270

271+
### Security Configuration
272+
273+
Configure the gateway's security middleware for your environment:
274+
275+
**Development Configuration:**
276+
```yaml
277+
host: "localhost" # Bind to localhost for development security
278+
port: 8080
279+
issuer: "http://localhost:8080"
280+
debug: true # Enables permissive Origin validation (enforce_localhost=False)
281+
282+
cors:
283+
allow_origins: ["http://localhost:3000", "https://dev.myapp.com"]
284+
allow_credentials: true
285+
```
286+
287+
**Production Configuration:**
288+
```yaml
289+
host: "0.0.0.0" # Can bind to all interfaces with proper origin validation
290+
port: 8080
291+
issuer: "https://gateway.myapp.com"
292+
debug: false # Enables strict Origin validation (enforce_localhost=True)
293+
294+
cors:
295+
allow_origins: ["https://myapp.com", "https://admin.myapp.com"] # Explicit production origins
296+
allow_credentials: true
297+
allow_methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
298+
allow_headers: ["Authorization", "Content-Type", "MCP-Protocol-Version"]
299+
```
300+
301+
**Security Behavior by Environment:**
302+
- **Development** (`debug=true`): Permissive origin validation for easy testing
303+
- **Production** (`debug=false`): Strict origin validation with localhost fallback for debugging
304+
203305
### Adding New MCP Services
204306

205307
1. **Configure the service** in `config.yaml`:
@@ -215,8 +317,9 @@ mcp_services:
215317
```
216318

217319
2. **The service will be automatically available** at `/{service-id}/mcp`
218-
3. **Backend services receive user context** via headers
219-
4. **All services must use the same OAuth provider** configured in the gateway
320+
3. **Service gets canonical URI** for token audience: `{issuer}/{service-id}/mcp`
321+
4. **Backend services receive user context** via headers
322+
5. **All services must use the same OAuth provider** configured in the gateway
220323

221324
### Code Style and Standards
222325

@@ -229,22 +332,28 @@ mcp_services:
229332

230333
### Testing
231334

232-
- **Unit test suite** - 15 test files covering individual components with mocking
233-
- **OAuth 2.1 component testing** - PKCE validation, token exchange, metadata endpoints
234-
- **Security boundary testing** - Token validation, redirect URI validation, error paths
235-
- **Configuration validation testing** - Single provider constraints, service configuration
335+
- **Comprehensive test suite** - 16+ test files covering all components with 197+ test cases
336+
- **OAuth 2.1 component testing** - PKCE validation, token exchange, metadata endpoints with canonical URIs
337+
- **Security middleware testing** - Origin validation, MCP protocol version validation, middleware integration
338+
- **Security boundary testing** - Token validation, redirect URI validation, audience binding, error paths
339+
- **Configuration validation testing** - Single provider constraints, service configuration, canonical URI generation
236340
- **Component isolation testing** - Mocked HTTP requests, isolated functionality testing
237-
- **pytest framework** with async support, HTTP client mocking, and component fixtures
238-
- **Test utilities** - PKCE generation helpers and crypto validation tools
341+
- **Integration testing** - End-to-end OAuth flows with service-specific token validation
342+
- **pytest framework** with async support, HTTP client mocking, FastAPI TestClient, and component fixtures
343+
- **Test utilities** - PKCE generation helpers, crypto validation tools, middleware test patterns
239344

240345
### Security Considerations
241346

242347
- **NEVER log sensitive data** (tokens, secrets, user data)
243348
- **Validate all input** using Pydantic models
244349
- **Use secure random generation** for codes and secrets
245-
- **Implement proper CORS** for web clients
350+
- **Configure Origin validation** appropriately for your environment
351+
- **Enable localhost enforcement** in production (`debug=False`)
352+
- **Validate MCP protocol versions** to ensure client compatibility
353+
- **Implement proper CORS** for web clients with explicit origins
246354
- **Enforce HTTPS** in production
247355
- **Validate redirect URIs** strictly
356+
- **Use service-specific canonical URIs** for proper token audience binding
248357

249358
### Production Deployment
250359

@@ -272,13 +381,53 @@ python -m src.gateway --config config.yaml
272381
ruff check src/ --fix
273382
ruff format src/
274383
275-
# Run tests
384+
# Run tests (all 197+ test cases)
276385
pytest
277386
387+
# Run specific test categories
388+
pytest tests/gateway/test_middleware.py # Security middleware tests
389+
pytest tests/api/test_metadata.py # Canonical URI tests
390+
pytest tests/integration/ # Integration tests
391+
278392
# Type checking (if mypy is added)
279393
mypy src/
280394
```
281395

396+
### Troubleshooting
397+
398+
**Origin Validation Issues:**
399+
```bash
400+
# 403 Unauthorized origin errors in production
401+
# Check CORS configuration and debug mode:
402+
debug: false # Should be false in production
403+
cors:
404+
allow_origins: ["https://yourapp.com"] # Add your domain
405+
406+
# For development, enable debug mode:
407+
debug: true # Allows more permissive origins
408+
```
409+
410+
**MCP Protocol Version Issues:**
411+
```bash
412+
# 400 Unsupported MCP protocol version
413+
# Ensure your client sends supported version header:
414+
curl -H "MCP-Protocol-Version: 2025-06-18" https://gateway.com/service/mcp
415+
416+
# Supported versions: 2025-06-18, 2025-03-26
417+
# Missing header is allowed (backend handles default)
418+
```
419+
420+
**Token Audience Issues:**
421+
```bash
422+
# 401 Invalid token errors
423+
# Verify token audience matches service canonical URI:
424+
# Expected: https://gateway.com/calculator/mcp
425+
# Not: https://gateway.com/calculator
426+
427+
# Check metadata endpoint for correct canonical URI:
428+
curl https://gateway.com/.well-known/oauth-protected-resource?service_id=calculator
429+
```
430+
282431
### Docker Development
283432
```bash
284433
# Build image
@@ -295,20 +444,25 @@ docker run -p 8080:8080 \
295444
### OAuth 2.1 Compliance
296445
- **PKCE required** for all authorization code flows
297446
- **Resource parameter** for audience binding per RFC 8707
447+
- **Service-specific canonical URIs** following format `{issuer}/{service-id}/mcp`
298448
- **Dynamic Client Registration** per RFC 7591
299449
- **Proper metadata endpoints** per RFC 8414 and RFC 9728
300450

301451
### MCP Protocol Support
302452
- **Streamable HTTP transport** as specified in MCP
453+
- **MCP protocol version validation** for specification compliance
303454
- **User context injection** for backend authorization
304455
- **Transparent proxying** maintains MCP protocol semantics
305456
- **Service-specific token scoping** prevents privilege escalation
306457

307458
### Security Architecture
308-
- **Service isolation** through audience-bound tokens
459+
- **Multi-layer security middleware** with DNS rebinding protection
460+
- **Environment-aware security** (development vs production modes)
461+
- **Service isolation** through audience-bound tokens with canonical URIs
309462
- **Single provider design** ensures consistent authentication
310463
- **Session management** with secure, signed sessions
311464
- **State validation** prevents CSRF attacks
465+
- **Origin validation** protects against cross-origin attacks
312466

313467
## Known Limitations
314468

src/api/metadata.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,38 @@ def get_protected_resource_metadata(
4848
}
4949

5050
if service_id:
51-
# Service-specific metadata
51+
# Service-specific metadata with canonical MCP URI
5252
service = self.config.mcp_services.get(service_id)
5353
if service:
54-
base_metadata["resource"] = f"{self.config.issuer}/{service_id}"
54+
# Generate canonical URI per RFC 8707 and MCP spec
55+
canonical_uri = self.get_service_canonical_uri(service_id)
56+
base_metadata["resource"] = canonical_uri
5557
base_metadata["scopes_supported"] = (
5658
service.scopes or self._get_supported_scopes()
5759
)
5860

5961
return base_metadata
6062

63+
def get_service_canonical_uri(self, service_id: str) -> str:
64+
"""Generate canonical URI for a service per RFC 8707 and MCP spec.
65+
66+
Returns the canonical URI that should be used as the audience
67+
for tokens intended for this specific MCP service.
68+
69+
Format: {issuer}/{service_id}/mcp
70+
Example: https://gateway.example.com/calculator/mcp
71+
"""
72+
# Normalize issuer by removing trailing slash
73+
issuer = self.config.issuer.rstrip("/")
74+
return f"{issuer}/{service_id}/mcp"
75+
76+
def get_all_service_canonical_uris(self) -> Dict[str, str]:
77+
"""Get canonical URIs for all configured services."""
78+
return {
79+
service_id: self.get_service_canonical_uri(service_id)
80+
for service_id in self.config.mcp_services.keys()
81+
}
82+
6183
def _get_supported_scopes(self) -> List[str]:
6284
"""Get all supported scopes from configured services."""
6385
scopes = {"read", "write"} # Default scopes

0 commit comments

Comments
 (0)