Vault.MCP / docs /hf-spaces-deployment-guide.md
Wothmag07's picture
Backend port changes
a2c1a36

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_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

#!/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_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

# 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:

  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