JWT Authentication Implementation
This document describes the JWT-based OAuth 2.1 authentication implementation for mcp-trino server.
Overview
The mcp-trino server implements OAuth 2.1 authentication using JWT Bearer tokens with server-level request interception. This ensures complete API protection for all MCP methods, not just tool execution.
Architecture
Authentication Flow
Flow Steps:
HTTP Request: Client sends request with
Authorization: Bearer <jwt-token>
headerContext Injection:
WithHTTPContextFunc
extracts token from headers into request contextServer-Level Authentication:
OnRequestInitialization
hook validates token before any processingMethod Execution: If authenticated, request proceeds to appropriate MCP handler
Security Model
Complete API Protection: ALL MCP methods require authentication
Server-Level Enforcement: Authentication applied before method-specific processing
JWT Validation: Simplified JWT parsing with claims extraction
Context-Based: Token and user info stored in request context
Implementation Details
1. Configuration
OAuth is controlled via environment variables:
TRINO_OAUTH_ENABLED=true # Enable OAuth authentication
JWT_SECRET=your-secret-key # JWT signing secret (REQUIRED - no default)
2. Context Management
Token Injection (cmd/main.go
):
contextFunc := func(ctx context.Context, r *http.Request) context.Context {
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
token := strings.TrimPrefix(authHeader, "Bearer ")
token = strings.TrimSpace(token)
ctx = auth.WithOAuthToken(ctx, token)
log.Printf("OAuth: Token extracted from request (length: %d)", len(token))
}
return ctx
}
Shared Context Keys (internal/auth/oauth.go
):
type contextKey string
const (
oauthTokenKey contextKey = "oauth_token"
userContextKey contextKey = "user"
)
func WithOAuthToken(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, oauthTokenKey, token)
}
func GetOAuthToken(ctx context.Context) (string, bool) {
token, ok := ctx.Value(oauthTokenKey).(string)
return token, ok
}
3. Server-Level Authentication
Request Initialization Hook (internal/auth/oauth.go
):
func CreateRequestAuthHook() func(context.Context, interface{}, interface{}) error {
return func(ctx context.Context, id interface{}, message interface{}) error {
// Use shared authentication function (DRY principle)
user, err := authenticateRequest(ctx)
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
log.Printf("OAuth: Authenticated user %s for request ID: %v", user.Username, id)
return nil // Allow request to proceed
}
}
Consolidated Authentication Function (internal/auth/oauth.go
):
func authenticateRequest(ctx context.Context) (*User, error) {
// Extract token from context
tokenString, ok := GetOAuthToken(ctx)
if !ok {
return nil, fmt.Errorf("authentication required: missing OAuth token")
}
// Validate JWT token with proper signature verification
user, err := validateJWT(tokenString)
if err != nil {
return nil, fmt.Errorf("authentication failed: %w", err)
}
return user, nil
}
Server Configuration (cmd/main.go
):
// Create hooks for server-level authentication
hooks := &server.Hooks{}
if trinoConfig.OAuthEnabled {
hooks.AddOnRequestInitialization(auth.CreateRequestAuthHook())
}
mcpServer := server.NewMCPServer("Trino MCP Server", Version,
server.WithToolCapabilities(true),
server.WithHooks(hooks),
)
4. JWT Validation
Secure JWT Parser with Signature Verification (internal/auth/oauth.go
):
func validateJWT(tokenString string) (*User, error) {
// Remove Bearer prefix if present
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
// Get cached JWT secret
secret, err := getJWTSecret()
if err != nil {
return nil, fmt.Errorf("JWT secret not configured: %w", err)
}
// Parse JWT with proper signature verification
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(secret), nil
})
if err != nil {
return nil, fmt.Errorf("failed to parse token: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return nil, fmt.Errorf("invalid token claims")
}
// Validate required claims
if err := validateJWTClaims(claims); err != nil {
return nil, fmt.Errorf("invalid token claims: %w", err)
}
// Extract user information
user := &User{
Subject: getStringClaim(claims, "sub"),
Username: getStringClaim(claims, "preferred_username"),
Email: getStringClaim(claims, "email"),
}
if user.Subject == "" {
return nil, fmt.Errorf("missing subject in token")
}
return user, nil
}
JWT Secret Caching (internal/auth/oauth.go
):
var (
jwtSecret string
jwtSecretOnce sync.Once
)
func getJWTSecret() (string, error) {
jwtSecretOnce.Do(func() {
jwtSecret = os.Getenv("JWT_SECRET")
})
if jwtSecret == "" {
return "", fmt.Errorf("JWT_SECRET environment variable is required")
}
return jwtSecret, nil
}
5. StreamableHTTP Integration
Modern HTTP Transport (cmd/main.go
):
// Create StreamableHTTP server instance
streamableServer := server.NewStreamableHTTPServer(
mcpServer,
server.WithEndpointPath("/mcp"),
server.WithHTTPContextFunc(contextFunc),
server.WithStateLess(false), // Enable session management
)
Protected API Surface
With the server-level authentication hook, ALL MCP methods are now protected:
✅
initialize
- Session establishment✅
tools/list
- List available tools✅
tools/call
- Execute tools✅
resources/list
- List available resources✅
resources/read
- Read resources✅
prompts/list
- List available prompts✅
prompts/get
- Get prompt templates✅ All other MCP methods
Testing
Generate Test Token
go run examples/generate_token.go
Test Authentication
# Complete authentication test
./test_server_auth.sh
Expected Results:
❌ Unauthenticated requests: Blocked with "authentication required"
✅ Authenticated requests: Allowed with proper JWT token
Manual Testing
# Valid token test
curl -X POST https://localhost:8080/mcp \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
-k
# Invalid token test (should fail)
curl -X POST https://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
-k
Client Configuration
Claude Code Configuration
The server supports both modern and legacy endpoints for backward compatibility:
Modern endpoint (recommended):
{
"mcpServers": {
"trino-oauth": {
"type": "http",
"url": "https://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
}
Legacy endpoint (backward compatibility):
{
"mcpServers": {
"trino-oauth": {
"type": "http",
"url": "https://localhost:8080/sse",
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
}
Both endpoints provide identical functionality and security.
Backward Compatibility
Endpoint Support
The server maintains backward compatibility by supporting both endpoints:
/mcp
✅ Recommended
Modern StreamableHTTP endpoint
/sse
✅ Legacy Support
Backward compatibility for existing clients
Migration Path
For existing clients using the /sse
endpoint:
No immediate changes required -
/sse
endpoint continues to workGradual migration - Update clients to use
/mcp
when convenientSame authentication - Both endpoints use identical OAuth implementation
Same functionality - All MCP methods available on both endpoints
Technical Implementation
Both endpoints are handled by the same mcpHandler
function:
// Shared MCP handler function for both endpoints
mcpHandler := func(w http.ResponseWriter, r *http.Request) {
// Same CORS, authentication, and processing logic
streamableServer.ServeHTTP(w, r)
}
// Add both endpoints
mux.HandleFunc("/mcp", mcpHandler) // Modern
mux.HandleFunc("/sse", mcpHandler) // Legacy
This ensures:
Identical behavior across both endpoints
Single codebase for maintenance
Consistent security implementation
Production Considerations
HTTPS Required: OAuth should always use HTTPS in production
JWT Signature Verification: ✅ IMPLEMENTED - Proper signature verification with HMAC-SHA256
Token Expiration: ✅ IMPLEMENTED - Proper token expiration checking with claims validation
Rate Limiting: Add rate limiting middleware after authentication
Audit Logging: Log all authentication attempts
Security Improvements Applied:
JWT secret caching with
sync.Once
patternNo insecure default fallbacks
Consolidated authentication logic
Graceful HTTP server shutdown
Security Features
Complete API Protection
Unlike tool-only middleware approaches, this implementation provides:
Server-Level Security: Authentication applied before any request processing
No Bypass Routes: Every MCP method requires authentication
Early Termination: Invalid requests rejected immediately
Context Propagation: User information available throughout request lifecycle
Error Handling
Clear Error Messages: Descriptive authentication failure messages
Proper HTTP Status Codes: Standard error codes for different failure types
Debug Logging: Comprehensive logging for troubleshooting
Migration Notes
From Tool-Only Middleware
If migrating from WithToolHandlerMiddleware
approach:
Remove tool-specific middleware configuration
Add server-level hooks with
OnRequestInitialization
Test all MCP methods for proper authentication
Update client configurations to include authentication headers
From SSE Transport
If migrating from Server-Sent Events:
Replace
NewSSEServer
withNewStreamableHTTPServer
Update client endpoints from
/sse
to/mcp
Update context functions from
WithSSEContextFunc
toWithHTTPContextFunc
Test session management compatibility
Troubleshooting
Common Issues
"authentication required": Missing or malformed Authorization header
"failed to parse token": JWT token corrupted (check for line breaks in curl commands)
"missing subject in token": JWT missing required
sub
claimSession ID errors: Ensure proper session establishment flow
Debug Logging
Enable detailed OAuth logging by checking server output:
OAuth: Token extracted from request (length: 193)
OAuth: Received token for request ID 1: eyJhbGciOiJub25lIi...
OAuth: Authenticated user testuser for request ID: 1
Implementation Status
✅ Server-Level Authentication: Complete API protection
✅ JWT Validation: Secure token parsing with proper signature verification
✅ StreamableHTTP Transport: Modern HTTP streaming
✅ Session Management: Proper MCP session handling
✅ Context Management: Shared context keys
✅ Error Handling: Descriptive error messages
✅ Testing Framework: Comprehensive test scripts
✅ Client Integration: Claude Code compatibility
✅ Security Enhancements:
JWT secret caching with
sync.Once
patternProper JWT signature verification (no ParseUnverified)
Consolidated authentication logic (DRY principle)
No insecure default fallbacks
Graceful HTTP server shutdown with timeout
The OAuth 2.1 implementation provides complete security for the mcp-trino server with modern transport, proper authentication for all MCP methods, and production-ready security features.
Last updated
Was this helpful?