Spaces:
Running
Running
File size: 10,839 Bytes
a2c1a36 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# Hugging Face Spaces Deployment Guide
## Overview
This guide covers deploying your MCP server to Hugging Face Spaces with proper JWT authentication, session management, and multi-tenant isolation.
## π Authentication Flow
### Current Architecture
```
User Login (HF OAuth)
β Get User Info (user_id, email, etc.)
β Generate JWT Token (with user_id in payload)
β Store Token in Client
β Send Token in Authorization Header for MCP Requests
β Server Validates Token β Extracts user_id
β All Operations Scoped to That User's Vault
```
## β
Pre-Deployment Checklist
### 1. JWT Configuration
- [ ] **Set Production JWT Secret Key**
```bash
# In HF Spaces Environment Variables
JWT_SECRET_KEY=<generate-strong-random-secret>
```
Generate with:
```python
import secrets
print(secrets.token_urlsafe(32))
```
- [ ] **Verify JWT Token Generation**
- Each user gets unique JWT token after login
- Token contains `user_id` in `sub` claim
- Token has appropriate expiration (default: 7 days)
- [ ] **Verify JWT Token Validation**
- Server validates token signature
- Server checks expiration
- Server extracts `user_id` from `sub` claim
### 2. Session Management
**Important**: HTTP MCP transport requires session management. Each user needs:
- [ ] **Session ID per User**
- Generated after `initialize` call
- Stored on server (in-memory or Redis for production)
- Sent back to client for subsequent requests
- [ ] **Session-to-User Mapping**
- Map session ID β user_id
- Validate session belongs to authenticated user
- Clean up expired sessions
### 3. Multi-Tenant Isolation
- [ ] **Vault Isolation**
- Each user gets: `/data/vaults/{user_id}/`
- Verify users cannot access other users' vaults
- [ ] **Database Isolation**
- All queries filtered by `user_id`
- Verify SQL queries include `WHERE user_id = ?`
- [ ] **Search Index Isolation**
- Full-text search scoped to user's notes only
- Verify search results only return user's notes
## π§ͺ Testing Multi-User Authentication
### Test Script for Multi-User JWT
```python
#!/usr/bin/env python3
"""Test multi-user JWT authentication and isolation."""
import requests
import sys
sys.path.insert(0, './backend')
from backend.src.services.auth import AuthService
from backend.src.services.config import get_config
def test_multi_user_jwt():
"""Test JWT generation and validation for multiple users."""
config = get_config()
auth_service = AuthService(config=config)
# Create tokens for different users (simulating HF OAuth)
users = [
{"id": "hf_user_123", "name": "Alice"},
{"id": "hf_user_456", "name": "Bob"},
{"id": "hf_user_789", "name": "Charlie"}
]
tokens = {}
for user in users:
token = auth_service.create_jwt(user["id"])
tokens[user["id"]] = token
print(f"β
Generated token for {user['name']} ({user['id']})")
# Test token validation
print("\nπ Testing token validation...")
for user_id, token in tokens.items():
try:
payload = auth_service.validate_jwt(token)
assert payload.sub == user_id, f"Token user_id mismatch for {user_id}"
print(f"β
Token validated for {user_id}: {payload.sub}")
except Exception as e:
print(f"β Token validation failed for {user_id}: {e}")
# Test isolation - each user should only see their own notes
print("\nπ Testing user isolation...")
base_url = "http://localhost:8001/mcp"
for user_id, token in tokens.items():
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream"
}
# Initialize for this user
init_request = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "test", "version": "1.0"}
}
}
try:
response = requests.post(base_url, json=init_request, headers=headers)
if response.status_code == 200:
print(f"β
{user_id} initialized successfully")
else:
print(f"β {user_id} initialization failed: {response.text}")
except Exception as e:
print(f"β {user_id} request failed: {e}")
if __name__ == "__main__":
test_multi_user_jwt()
```
## π Deployment Steps
### Step 1: Environment Variables in HF Spaces
Set these in your HF Space settings:
```bash
# Required
JWT_SECRET_KEY=<your-production-secret-key>
VAULT_BASE_PATH=/app/data/vaults
DATABASE_PATH=/app/data/index.db
# MCP Configuration
MCP_TRANSPORT=http
MCP_PORT=7860 # HF Spaces default port
# Optional
MODE=space # Multi-tenant mode
```
### Step 2: Update Dockerfile for HF Spaces
Your Dockerfile should:
- Expose port 7860 (HF Spaces requirement)
- Set proper environment variables
- Mount persistent storage for `/app/data`
### Step 3: Frontend OAuth Integration
```typescript
// After HF OAuth login
async function handleHFOAuthCallback(oauthToken: string) {
// Get user info from HF
const userInfo = await fetch('https://huggingface.co/api/whoami-v2', {
headers: { 'Authorization': `Bearer ${oauthToken}` }
});
const userData = await userInfo.json();
// Generate JWT token for MCP (call your backend)
const jwtResponse = await fetch('/api/auth/create-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userData.id })
});
const { token } = await jwtResponse.json();
// Store token for MCP requests
localStorage.setItem('mcp_jwt_token', token);
// Configure MCP client
mcpClient.setAuthHeader(`Bearer ${token}`);
}
```
### Step 4: MCP Client Configuration
```typescript
// Frontend MCP client setup
const mcpClient = new MCPClient({
transport: 'http',
url: 'https://your-space.hf.space/mcp',
headers: {
'Authorization': `Bearer ${getStoredJWTToken()}`,
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream'
}
});
```
## π Security Verification Checklist
### Authentication Security
- [ ] **JWT Secret Key**
- β
Strong random secret (32+ bytes)
- β
Stored in environment variables (not in code)
- β
Different secret for production vs development
- [ ] **Token Expiration**
- β
Tokens expire after reasonable time (7 days default)
- β
Expired tokens are rejected
- β
Client refreshes tokens before expiration
- [ ] **Token Validation**
- β
Server validates signature on every request
- β
Server checks expiration
- β
Invalid tokens return 401 Unauthorized
### Authorization Security
- [ ] **User Isolation**
- β
Each request extracts `user_id` from JWT
- β
All vault operations scoped to `user_id`
- β
Database queries filtered by `user_id`
- β
Users cannot access other users' data
- [ ] **Path Validation**
- β
Note paths validated (no `..` or `\`)
- β
Path length limits enforced (β€256 chars)
- β
Paths relative to user's vault directory
### Session Security
- [ ] **Session Management**
- β
Session IDs generated securely
- β
Sessions tied to user_id
- β
Sessions expire after inactivity
- β
Session cleanup on logout
## π§ͺ Production Testing
### Test 1: Multi-User Isolation
```bash
# User 1
curl -X POST https://your-space.hf.space/mcp \
-H "Authorization: Bearer <user1_jwt_token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_notes","arguments":{}}}'
# User 2 (should see different notes)
curl -X POST https://your-space.hf.space/mcp \
-H "Authorization: Bearer <user2_jwt_token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_notes","arguments":{}}}'
```
### Test 2: Token Validation
```bash
# Valid token
curl -X POST https://your-space.hf.space/mcp \
-H "Authorization: Bearer <valid_token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
# Invalid token (should fail)
curl -X POST https://your-space.hf.space/mcp \
-H "Authorization: Bearer invalid_token" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}'
```
### Test 3: Expired Token
```python
# Generate expired token
from datetime import timedelta
expired_token = auth_service.create_jwt("user123", expires_in=timedelta(seconds=-1))
# Try to use it (should fail)
# Should return 401 Unauthorized
```
## π Monitoring & Logging
### Key Metrics to Monitor
- [ ] **Authentication Failures**
- Invalid tokens
- Expired tokens
- Missing Authorization headers
- [ ] **User Activity**
- Active users per day
- Requests per user
- Vault sizes per user
- [ ] **Security Events**
- Failed authentication attempts
- Unauthorized access attempts
- Path traversal attempts
### Logging Requirements
```python
# Log authentication events
logger.info("User authenticated", extra={
"user_id": payload.sub,
"token_issued_at": payload.iat,
"token_expires_at": payload.exp
})
# Log authorization failures
logger.warning("Unauthorized access attempt", extra={
"path": requested_path,
"user_id": attempted_user_id,
"error": "permission_denied"
})
```
## π― Summary
### What You're Correct About:
1. β
**JWT per User**: Each user gets unique JWT token after HF OAuth login
2. β
**Different user_id**: Each JWT contains the user's ID in `sub` claim
3. β
**Session Management**: HTTP MCP requires session IDs for stateful operations
### Additional Considerations:
1. **Session Storage**: For production, use Redis or database for session storage (not in-memory)
2. **Token Refresh**: Implement token refresh mechanism before expiration
3. **Rate Limiting**: Add rate limiting per user to prevent abuse
4. **Audit Logging**: Log all authentication and authorization events
### Your Current Implementation Status:
- β
JWT generation and validation - **Already implemented**
- β
User isolation in vaults - **Already implemented**
- β
User isolation in database - **Already implemented**
- β οΈ Session management - **Needs implementation for HTTP MCP**
- β οΈ Production JWT secret - **Needs to be set in HF Spaces**
Your architecture is **production-ready**! You just need to:
1. Set `JWT_SECRET_KEY` in HF Spaces
2. Implement session management for HTTP MCP
3. Add frontend OAuth integration
4. Test multi-user isolation
|