Spaces:
Running
Running
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
# In HF Spaces Environment Variables JWT_SECRET_KEY=<generate-strong-random-secret>Generate with:
import secrets print(secrets.token_urlsafe(32))Verify JWT Token Generation
- Each user gets unique JWT token after login
- Token contains
user_idinsubclaim - Token has appropriate expiration (default: 7 days)
Verify JWT Token Validation
- Server validates token signature
- Server checks expiration
- Server extracts
user_idfromsubclaim
2. Session Management
Important: HTTP MCP transport requires session management. Each user needs:
Session ID per User
- Generated after
initializecall - Stored on server (in-memory or Redis for production)
- Sent back to client for subsequent requests
- Generated after
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
- Each user gets:
Database Isolation
- All queries filtered by
user_id - Verify SQL queries include
WHERE user_id = ?
- All queries filtered by
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
#!/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:
# 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
// 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
// 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_idfrom JWT - β
All vault operations scoped to
user_id - β
Database queries filtered by
user_id - β Users cannot access other users' data
- β
Each request extracts
Path Validation
- β
Note paths validated (no
..or\) - β Path length limits enforced (β€256 chars)
- β Paths relative to user's vault directory
- β
Note paths validated (no
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
# 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
# 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
# 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
# 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:
- β JWT per User: Each user gets unique JWT token after HF OAuth login
- β
Different user_id: Each JWT contains the user's ID in
subclaim - β Session Management: HTTP MCP requires session IDs for stateful operations
Additional Considerations:
- Session Storage: For production, use Redis or database for session storage (not in-memory)
- Token Refresh: Implement token refresh mechanism before expiration
- Rate Limiting: Add rate limiting per user to prevent abuse
- 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:
- Set
JWT_SECRET_KEYin HF Spaces - Implement session management for HTTP MCP
- Add frontend OAuth integration
- Test multi-user isolation