Skip to content

Commit 7a8afd1

Browse files
authored
Add remote MCP authentication documentation (#1981)
Add comprehensive remote MCP authentication documentation This adds detailed documentation about how ToolHive handles remote MCP server authentication, including compliance with various RFCs and the MCP authorization specification. The documentation covers: - Specification compliance (RFC 9728, RFC 8414, RFC 7591) - Authentication flow and discovery priority chain - Well-known endpoint discovery for issuer mismatch handling - Dynamic client registration - Security features and configuration options - Implementation details and key components This documentation helps developers understand the authentication architecture and serves as a reference for the implementation that was fixed in PR #1980.
1 parent 0e2e0e2 commit 7a8afd1

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

docs/remote-mcp-authentication.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# ToolHive Remote MCP Server Authentication Analysis
2+
3+
This document analyzes how ToolHive handles remote MCP server authentication and its compliance with the [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization).
4+
5+
## Executive Summary
6+
7+
ToolHive is **highly compliant** with the MCP authorization specification, implementing all required features including RFC 9728 (Protected Resource Metadata), RFC 8414 (Authorization Server Metadata), RFC 7591 (Dynamic Client Registration), and PKCE support.
8+
9+
## Specification Compliance
10+
11+
### ✅ Fully Compliant Features
12+
13+
#### 1. WWW-Authenticate Header Handling
14+
- **Location**: [`pkg/auth/discovery/discovery.go:159-233`](../pkg/auth/discovery/discovery.go#L159)
15+
- Correctly parses `Bearer` authentication scheme
16+
- Extracts `realm` and `resource_metadata` parameters as per RFC 9728
17+
- Handles error and error_description parameters
18+
19+
#### 2. Protected Resource Metadata (RFC 9728)
20+
- **Location**: [`pkg/auth/discovery/discovery.go:531-593`](../pkg/auth/discovery/discovery.go#L531)
21+
- Fetches metadata from `resource_metadata` URL in WWW-Authenticate header
22+
- Validates HTTPS requirement (with localhost exception for development)
23+
- Verifies required `resource` field presence
24+
- Extracts and processes `authorization_servers` array
25+
26+
#### 3. Authorization Server Discovery (RFC 8414)
27+
- **Location**: [`pkg/auth/discovery/discovery.go:595-621`](../pkg/auth/discovery/discovery.go#L595)
28+
- Validates each authorization server in metadata
29+
- Discovers actual issuer via OIDC/.well-known endpoints
30+
- Handles issuer mismatch cases where metadata URL differs from actual issuer
31+
- Accepts the authoritative issuer from well-known endpoints per RFC 8414
32+
33+
#### 4. Dynamic Client Registration (RFC 7591)
34+
- **Location**: [`pkg/auth/oauth/dynamic_registration.go:82-200`](../pkg/auth/oauth/dynamic_registration.go#L82)
35+
- Automatically registers OAuth clients when no credentials provided
36+
- Uses PKCE flow with `token_endpoint_auth_method: "none"`
37+
- Supports both manual client configuration and automatic registration
38+
39+
#### 5. PKCE Support
40+
- **Location**: [`pkg/auth/oauth/dynamic_registration.go:52`](../pkg/auth/oauth/dynamic_registration.go#L52)
41+
- Enabled by default for enhanced security
42+
- Required for public clients as per OAuth 2.1
43+
44+
## Authentication Flow
45+
46+
### Initial Detection
47+
When ToolHive connects to a remote MCP server ([`pkg/runner/remote_auth.go:27-87`](../pkg/runner/remote_auth.go#L27)):
48+
49+
1. Makes test request to the remote server (GET, then optionally POST)
50+
2. Checks for 401 Unauthorized response with WWW-Authenticate header
51+
3. Parses authentication requirements from the header
52+
53+
### Discovery Priority Chain
54+
ToolHive follows this priority order for discovering the OAuth issuer ([`pkg/runner/remote_auth.go:95-145`](../pkg/runner/remote_auth.go#L95)):
55+
56+
1. **Configured Issuer**: Uses `--remote-auth-issuer` flag if provided
57+
2. **Realm-Derived**: Derives from `realm` parameter in WWW-Authenticate header (RFC 8414)
58+
3. **Resource Metadata**: Fetches from `resource_metadata` URL (RFC 9728)
59+
4. **Well-Known Discovery**: Probes server's well-known endpoints to discover actual issuer (handles issuer mismatch)
60+
5. **URL-Derived**: Falls back to deriving from the remote URL (last resort)
61+
62+
### Authentication Branches
63+
64+
```mermaid
65+
graph TD
66+
A[Remote MCP Server Request] --> B{401 Response?}
67+
B -->|No| C[No Authentication Required]
68+
B -->|Yes| D{WWW-Authenticate Header?}
69+
D -->|No| E[No Authentication Required]
70+
D -->|Yes| F{Parse Header}
71+
72+
F --> G{Has Realm URL?}
73+
G -->|Yes| H[Derive Issuer from Realm]
74+
H --> I[OIDC Discovery]
75+
76+
F --> J{Has resource_metadata?}
77+
J -->|Yes| K[Fetch Resource Metadata]
78+
K --> L[Validate Auth Servers]
79+
L --> M[Use First Valid Server]
80+
81+
F --> S{No Realm/Metadata?}
82+
S -->|Yes| T[Probe Well-Known Endpoints]
83+
T --> U{Found Valid Issuer?}
84+
U -->|Yes| V[Use Discovered Issuer]
85+
U -->|No| W[Derive from URL]
86+
87+
I --> N{Client Credentials?}
88+
M --> N
89+
V --> N
90+
W --> N
91+
N -->|No| O[Dynamic Registration]
92+
N -->|Yes| P[OAuth Flow]
93+
O --> P
94+
95+
P --> Q[Get Access Token]
96+
Q --> R[Authenticated Request]
97+
```
98+
99+
## Realm Handling
100+
101+
When the server advertises a realm ([`pkg/auth/discovery/discovery.go:316-345`](../pkg/auth/discovery/discovery.go#L316)):
102+
103+
1. Validates realm as HTTPS URL (RFC 8414 requirement)
104+
2. Strips query and fragment components to create valid issuer
105+
3. Uses as OAuth issuer for endpoint discovery
106+
107+
Example:
108+
- Realm: `https://auth.example.com/realm/mcp?param=value#fragment`
109+
- Derived Issuer: `https://auth.example.com/realm/mcp`
110+
111+
## Resource Metadata Processing
112+
113+
When `resource_metadata` URL is provided:
114+
115+
1. **Fetch Metadata**: GET request to the URL with JSON accept header
116+
2. **Validate Response**: Ensures HTTPS, checks content-type, validates `resource` field
117+
3. **Process Authorization Servers**:
118+
- Iterates through `authorization_servers` array
119+
- Validates each server via OIDC discovery
120+
- Uses first valid server found
121+
4. **Handle Issuer Mismatch**: Supports cases where metadata URL differs from actual issuer
122+
123+
## Well-Known Endpoint Discovery
124+
125+
When no realm URL or resource metadata is provided ([`pkg/runner/remote_auth.go:175-211`](../pkg/runner/remote_auth.go#L175)):
126+
127+
1. **Derive Base URL**: Creates a base URL from the server URL
128+
2. **Probe Well-Known Endpoints**: Attempts to fetch OAuth metadata without requiring issuer match
129+
3. **Accept Authoritative Issuer**: Uses the issuer from the well-known response as authoritative per RFC 8414
130+
4. **Log Mismatch**: Records when discovered issuer differs from server URL for debugging
131+
132+
This approach handles cases where the OAuth provider's issuer differs from the server's public URL, such as when using CDN or worker deployments.
133+
134+
## Dynamic Client Registration Flow
135+
136+
When no client credentials are provided ([`pkg/auth/oauth/dynamic_registration.go`](../pkg/auth/oauth/dynamic_registration.go)):
137+
138+
1. **Discover Registration Endpoint**: Via OIDC discovery or resource metadata
139+
2. **Create Registration Request**:
140+
```json
141+
{
142+
"client_name": "ToolHive MCP Client",
143+
"redirect_uris": ["http://localhost:8765/callback"],
144+
"token_endpoint_auth_method": "none",
145+
"grant_types": ["authorization_code"],
146+
"response_types": ["code"]
147+
}
148+
```
149+
3. **Register Client**: POST to registration endpoint
150+
4. **Store Credentials**: Use returned client_id (and client_secret if provided)
151+
5. **Proceed with OAuth Flow**: Using registered credentials
152+
153+
## Security Features
154+
155+
### HTTPS Enforcement
156+
- All OAuth endpoints must use HTTPS
157+
- Exception for localhost/127.0.0.1 for development
158+
- Validates all discovered URLs
159+
160+
### PKCE by Default
161+
- Automatically enabled for all OAuth flows
162+
- Required for public clients (no client_secret)
163+
- Provides protection against authorization code interception
164+
165+
### Token Handling
166+
- Secure token storage in memory
167+
- Automatic token refresh support
168+
- Token passed via Authorization header to remote server
169+
170+
### Configurable Timeouts
171+
- Authentication detection: 10 seconds default
172+
- OAuth flow: 5 minutes default
173+
- HTTP operations: 30 seconds default
174+
175+
## Configuration Options
176+
177+
### CLI Flags for Remote Authentication
178+
179+
```bash
180+
# Automatic discovery (recommended)
181+
thv run https://remote-mcp-server.com
182+
183+
# Manual OAuth configuration
184+
thv run https://remote-mcp-server.com \
185+
--remote-auth-issuer https://auth.example.com \
186+
--remote-auth-client-id my-client-id \
187+
--remote-auth-client-secret my-secret \
188+
--remote-auth-scopes "openid,profile,mcp"
189+
190+
# Skip browser for headless environments
191+
thv run https://remote-mcp-server.com \
192+
--remote-auth-skip-browser \
193+
--remote-auth-timeout 2m
194+
```
195+
196+
### Registry Configuration
197+
198+
Remote servers can be configured in the registry with OAuth settings:
199+
200+
```json
201+
{
202+
"version": "1.0.0",
203+
"last_updated": "2025-01-12T00:00:00Z",
204+
"remote_servers": {
205+
"example-remote": {
206+
"url": "https://remote-mcp-server.com",
207+
"description": "Remote MCP server with OAuth authentication",
208+
"tier": "community",
209+
"status": "active",
210+
"transport": "sse",
211+
"tools": ["tool1", "tool2"],
212+
"tags": ["remote", "oauth"],
213+
"headers": [
214+
{
215+
"name": "X-API-Key",
216+
"description": "API key for authentication",
217+
"required": true,
218+
"secret": true
219+
}
220+
],
221+
"oauth_config": {
222+
"issuer": "https://auth.example.com",
223+
"client_id": "optional-client-id",
224+
"scopes": ["openid", "profile", "mcp"],
225+
"callback_port": 8765,
226+
"use_pkce": true,
227+
"oauth_params": {
228+
"prompt": "consent"
229+
}
230+
}
231+
}
232+
}
233+
}
234+
```
235+
236+
The `oauth_config` section supports:
237+
- `issuer`: OIDC issuer URL for discovery
238+
- `authorize_url` & `token_url`: Manual OAuth endpoints (when not using OIDC)
239+
- `client_id`: Pre-configured client ID (optional, will use dynamic registration if not provided)
240+
- `scopes`: OAuth scopes to request
241+
- `callback_port`: Specific port for OAuth callback
242+
- `use_pkce`: Enable PKCE (defaults to true)
243+
- `oauth_params`: Additional OAuth parameters
244+
245+
## Implementation Details
246+
247+
### Key Components
248+
249+
1. **RemoteAuthHandler** ([`pkg/runner/remote_auth.go`](../pkg/runner/remote_auth.go))
250+
- Main entry point for remote authentication
251+
- Coordinates discovery and OAuth flow
252+
253+
2. **Discovery Package** ([`pkg/auth/discovery/`](../pkg/auth/discovery/))
254+
- WWW-Authenticate parsing
255+
- Resource metadata fetching
256+
- Authorization server validation
257+
258+
3. **OAuth Package** ([`pkg/auth/oauth/`](../pkg/auth/oauth/))
259+
- OIDC discovery
260+
- Dynamic client registration
261+
- OAuth flow execution with PKCE
262+
263+
### Error Handling
264+
265+
- Graceful fallback through discovery chain
266+
- Clear error messages for debugging
267+
- Retry logic for transient failures
268+
- Timeout protection for all operations
269+
270+
## Compliance Summary
271+
272+
| Specification | Status | Implementation |
273+
|--------------|--------|----------------|
274+
| RFC 9728 (Protected Resource Metadata) | ✅ Compliant | Full implementation with validation |
275+
| RFC 8414 (Authorization Server Metadata) | ✅ Compliant | Accepts authoritative issuer from well-known endpoints |
276+
| RFC 7591 (Dynamic Client Registration) | ✅ Compliant | Automatic registration when needed |
277+
| OAuth 2.1 PKCE | ✅ Compliant | Enabled by default |
278+
| WWW-Authenticate Parsing | ✅ Compliant | Supports Bearer with realm/resource_metadata |
279+
| Multiple Auth Servers | ✅ Compliant | Iterates and validates all servers |
280+
| Resource Parameter (RFC 8707) | ⚠️ Partial | Infrastructure ready, not yet sent in requests |
281+
| Token Audience Validation | ⚠️ Partial | Server-side validation support ready |
282+
283+
## Future Enhancements
284+
285+
While ToolHive is highly compliant with the current MCP specification, potential improvements include:
286+
287+
1. **Resource Parameter**: Add explicit `resource` parameter to OAuth requests (infrastructure exists)
288+
2. **Token Audience Validation**: Enhanced client-side validation of token audience claims
289+
3. **Refresh Token Rotation**: Implement automatic refresh token rotation for long-lived sessions
290+
4. **Client Credential Caching**: Persist dynamically registered clients across sessions
291+
292+
## Conclusion
293+
294+
ToolHive's remote MCP server authentication implementation is comprehensive and standards-compliant, providing:
295+
296+
- Full support for the MCP authorization specification
297+
- Automatic discovery and configuration
298+
- Dynamic client registration for zero-configuration setup
299+
- Strong security defaults with PKCE and HTTPS enforcement
300+
- Flexible configuration for various deployment scenarios
301+
302+
The implementation correctly handles all specified authentication flows and provides a robust foundation for secure MCP server communication.

0 commit comments

Comments
 (0)