Proxy Authentication¶
Configure MCP Gateway to work with authentication proxies for enterprise deployments.
Overview¶
MCP Gateway supports proxy authentication, allowing you to disable built-in JWT authentication and rely on an upstream authentication proxy. This is essential for enterprise deployments where authentication is centralized through OAuth2, SAML, or other identity providers.
Architecture¶
Standard JWT Authentication Flow¶
sequenceDiagram
participant Client
participant Gateway as MCP Gateway
participant MCP as MCP Server
Client->>Client: Generate JWT Token
Client->>Gateway: Request + Bearer Token
Gateway->>Gateway: Validate JWT
alt Valid Token
Gateway->>MCP: Forward Request
MCP-->>Gateway: Response
Gateway-->>Client: Response
else Invalid Token
Gateway-->>Client: 401 Unauthorized
end
Proxy Authentication Flow¶
sequenceDiagram
participant User
participant Proxy as Auth Proxy
participant IDP as Identity Provider
participant Gateway as MCP Gateway
participant MCP as MCP Server
User->>Proxy: Request
Proxy->>IDP: Validate Session
IDP-->>Proxy: User Identity
Proxy->>Gateway: Request + X-Authenticated-User
Gateway->>Gateway: Extract User from Header
Gateway->>MCP: Forward Request
MCP-->>Gateway: Response
Gateway-->>Proxy: Response
Proxy-->>User: Response
Configuration¶
Environment Variables¶
Variable | Default | Description |
---|---|---|
MCP_CLIENT_AUTH_ENABLED | true | Enable/disable JWT authentication for MCP operations |
TRUST_PROXY_AUTH | false | Trust proxy authentication headers (required when MCP_CLIENT_AUTH_ENABLED=false ) |
PROXY_USER_HEADER | X-Authenticated-User | Header containing authenticated username from proxy |
AUTH_REQUIRED | true | Controls admin UI authentication (independent of MCP auth) |
Security Notice
Only set MCP_CLIENT_AUTH_ENABLED=false
when MCP Gateway is deployed behind a trusted authentication proxy. Setting TRUST_PROXY_AUTH=true
explicitly acknowledges this security requirement.
Basic Configuration¶
# Disable MCP client JWT authentication
MCP_CLIENT_AUTH_ENABLED=false
# Trust proxy authentication headers
TRUST_PROXY_AUTH=true
# Header containing authenticated user
PROXY_USER_HEADER=X-Authenticated-User
# Keep admin UI protected
AUTH_REQUIRED=true
BASIC_AUTH_USER=admin
BASIC_AUTH_PASSWORD=secure-password
Deployment Patterns¶
Pattern 1: OAuth2 Proxy¶
graph LR
User[User] -->|HTTPS| LB[Load Balancer]
LB --> OAuth[OAuth2 Proxy]
OAuth -->|X-Auth-Request-User| Gateway[MCP Gateway]
Gateway --> MCP1[MCP Server 1]
Gateway --> MCP2[MCP Server 2]
OAuth -.->|OAuth Flow| IDP[Google/GitHub/etc]
style OAuth fill:#f9f,stroke:#333,stroke-width:2px
style Gateway fill:#bbf,stroke:#333,stroke-width:2px
Docker Compose Example¶
version: '3.8'
services:
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.5.0
command:
- --http-address=0.0.0.0:4180
- --upstream=http://mcp-gateway:4444
- --email-domain=*
- --pass-user-headers=true
- --set-xauthrequest=true
- --skip-provider-button=true
environment:
OAUTH2_PROXY_CLIENT_ID: ${OAUTH_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${OAUTH_CLIENT_SECRET}
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_PROVIDER: google
ports:
- "4180:4180"
networks:
- mcp-network
mcp-gateway:
image: ghcr.io/contingentai/mcp-gateway:latest
environment:
MCP_CLIENT_AUTH_ENABLED: "false"
TRUST_PROXY_AUTH: "true"
PROXY_USER_HEADER: "X-Auth-Request-Email"
AUTH_REQUIRED: "true"
BASIC_AUTH_USER: ${ADMIN_USER}
BASIC_AUTH_PASSWORD: ${ADMIN_PASSWORD}
DATABASE_URL: postgresql://postgres:password@db:5432/mcp
depends_on:
- db
networks:
- mcp-network
db:
image: postgres:15
environment:
POSTGRES_DB: mcp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- mcp-network
networks:
mcp-network:
driver: bridge
volumes:
postgres_data:
Pattern 2: Kubernetes with Istio¶
graph TB
subgraph "Kubernetes Cluster"
subgraph "Istio Service Mesh"
IG[Istio Gateway] --> VS[Virtual Service]
VS --> AuthZ[Authorization Policy]
AuthZ --> Gateway[MCP Gateway Pod]
end
Gateway --> MCP1[MCP Server Pod 1]
Gateway --> MCP2[MCP Server Pod 2]
OIDC[OIDC Provider] -.->|JWT Validation| AuthZ
end
User[User] -->|HTTPS + JWT| IG
style AuthZ fill:#f96,stroke:#333,stroke-width:2px
style Gateway fill:#bbf,stroke:#333,stroke-width:2px
Kubernetes Manifests¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-gateway
namespace: mcp-system
spec:
replicas: 3
selector:
matchLabels:
app: mcp-gateway
template:
metadata:
labels:
app: mcp-gateway
version: v1
spec:
containers:
- name: mcp-gateway
image: ghcr.io/contingentai/mcp-gateway:latest
env:
- name: MCP_CLIENT_AUTH_ENABLED
value: "false"
- name: TRUST_PROXY_AUTH
value: "true"
- name: PROXY_USER_HEADER
value: "X-User-Id"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: mcp-gateway-secrets
key: database-url
ports:
- containerPort: 4444
name: http
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: mcp-gateway-jwt
namespace: mcp-system
spec:
selector:
matchLabels:
app: mcp-gateway
jwtRules:
- issuer: "https://accounts.google.com"
jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
outputPayloadToHeader: "X-User-Id"
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: mcp-gateway-authz
namespace: mcp-system
spec:
selector:
matchLabels:
app: mcp-gateway
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]
Pattern 3: API Gateway (Kong)¶
graph LR
subgraph "Kong API Gateway"
Plugin[OIDC Plugin] --> Route[Route]
Route --> Service[Service]
end
User[User] -->|HTTPS| Plugin
Service -->|X-Consumer-Username| Gateway[MCP Gateway]
Gateway --> MCP[MCP Servers]
Plugin -.->|OIDC Flow| IDP[Keycloak/Auth0]
style Plugin fill:#f9f,stroke:#333,stroke-width:2px
style Gateway fill:#bbf,stroke:#333,stroke-width:2px
Kong Configuration¶
services:
- name: mcp-gateway
url: http://mcp-gateway:4444
routes:
- name: mcp-route
paths:
- /mcp
plugins:
- name: oidc
config:
client_id: mcp-client
client_secret: ${OIDC_SECRET}
discovery: https://auth.example.com/.well-known/openid-configuration
introspection_endpoint: https://auth.example.com/introspect
bearer_only: "yes"
realm: mcp-gateway
header_names:
- X-Consumer-Username:preferred_username
- X-Consumer-Id:sub
Common Proxy Configurations¶
Authelia¶
authentication_backend:
ldap:
url: ldaps://ldap.example.com
base_dn: dc=example,dc=com
access_control:
default_policy: deny
rules:
- domain: mcp.example.com
policy: two_factor
subject:
- group:mcp-users
# Headers forwarded to backend
authorization:
headers:
Remote-User: username
Remote-Email: email
Remote-Groups: groups
MCP Gateway configuration:
Cloudflare Access¶
graph LR
User[User] -->|HTTPS| CF[Cloudflare Edge]
CF -->|Cf-Access-Jwt-Assertion| Gateway[MCP Gateway]
CF -.->|SAML/OIDC| IDP[Identity Provider]
style CF fill:#f90,stroke:#333,stroke-width:2px
style Gateway fill:#bbf,stroke:#333,stroke-width:2px
Configuration:
MCP_CLIENT_AUTH_ENABLED=false
TRUST_PROXY_AUTH=true
PROXY_USER_HEADER=Cf-Access-Authenticated-User-Email
AWS ALB with Cognito¶
Header Passthrough¶
When using proxy authentication, you often need to pass additional headers to downstream MCP servers:
# Enable header passthrough
ENABLE_HEADER_PASSTHROUGH=true
# Headers to pass through (JSON array)
DEFAULT_PASSTHROUGH_HEADERS='["X-Tenant-Id", "X-Request-Id", "X-Authenticated-User", "X-Groups"]'
Security Considerations¶
Network Isolation¶
graph TB
subgraph "DMZ"
WAF[WAF] --> LB[Load Balancer]
LB --> Proxy[Auth Proxy]
end
subgraph "Private Network"
Proxy -->|Internal Only| Gateway[MCP Gateway]
Gateway --> MCP1[MCP Server 1]
Gateway --> MCP2[MCP Server 2]
end
Internet[Internet] -->|HTTPS| WAF
style Proxy fill:#f96,stroke:#333,stroke-width:2px
style Gateway fill:#bbf,stroke:#333,stroke-width:2px
Critical Security Requirements
- Never expose MCP Gateway directly to the internet when proxy auth is enabled
- Use TLS for all communication between proxy and gateway
- Implement network policies to ensure only the proxy can reach the gateway
- Validate proxy certificates in production environments
- Monitor authentication logs for suspicious activity
Recommended Security Headers¶
Configure your proxy to add these security headers:
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;
# Remove sensitive headers
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# Pass authentication headers
proxy_set_header X-Authenticated-User $remote_user;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Testing¶
Verify Configuration¶
# Test without authentication (should fail or return anonymous)
curl -v http://localhost:4444/tools
# Test with proxy header
curl -H "X-Authenticated-User: john.doe@example.com" \
http://localhost:4444/tools
# Test WebSocket with proxy header
wscat -c ws://localhost:4444/ws \
-H "X-Authenticated-User: john.doe@example.com"
Health Checks¶
Configure your load balancer to use these endpoints:
Endpoint | Purpose | Expected Response |
---|---|---|
/health | Liveness probe | 200 OK |
/ready | Readiness probe | 200 OK when ready |
/metrics | Prometheus metrics | Metrics in text format |
Troubleshooting¶
Common Issues¶
Getting 401 Unauthorized with proxy headers
Check these settings:
- Verify
MCP_CLIENT_AUTH_ENABLED=false
- Ensure
TRUST_PROXY_AUTH=true
- Confirm header name matches
PROXY_USER_HEADER
- Check proxy is sending the header:
Warning: MCP auth disabled without trust
You're seeing:
Solution: Set TRUST_PROXY_AUTH=true
to acknowledge proxy authentication.
WebSocket connections fail
Common causes:
- Proxy not passing headers on WebSocket upgrade
- Missing WebSocket support in proxy
nginx fix:
How to handle multiple authentication methods?
Use virtual servers with different auth configs:
Migration Guide¶
From JWT to Proxy Authentication¶
graph LR
subgraph "Phase 1: Preparation"
A1[Document Current Auth] --> A2[Deploy Proxy]
A2 --> A3[Test Proxy Auth]
end
subgraph "Phase 2: Dual Mode"
B1[Enable Both Auth] --> B2[Migrate Clients]
B2 --> B3[Monitor Logs]
end
subgraph "Phase 3: Proxy Only"
C1[Disable JWT Auth] --> C2[Remove JWT Code]
C2 --> C3[Document Change]
end
A3 --> B1
B3 --> C1
Step-by-Step Migration¶
Performance Considerations¶
Caching User Identity¶
graph LR
subgraph "With Caching"
Proxy1[Auth Proxy] --> Cache{Redis Cache}
Cache -->|Hit| Gateway1[MCP Gateway]
Cache -->|Miss| IDP1[IDP]
IDP1 --> Cache
end
subgraph "Without Caching"
Proxy2[Auth Proxy] --> IDP2[IDP]
IDP2 --> Gateway2[MCP Gateway]
end
style Cache fill:#9f9,stroke:#333,stroke-width:2px
Configure Redis caching for better performance:
# Enable Redis cache
CACHE_TYPE=redis
REDIS_URL=redis://localhost:6379/0
# Cache user sessions
SESSION_TTL=3600 # 1 hour
Monitoring¶
Key Metrics¶
Metric | Description | Alert Threshold |
---|---|---|
mcp_auth_failures_total | Failed authentication attempts | > 10/min |
mcp_proxy_header_missing | Requests without proxy header | > 5/min |
mcp_auth_latency_seconds | Authentication processing time | > 1s p99 |
Grafana Dashboard¶
{
"dashboard": {
"title": "MCP Gateway - Proxy Auth",
"panels": [
{
"title": "Auth Success Rate",
"targets": [{
"expr": "rate(mcp_auth_success_total[5m]) / rate(mcp_auth_attempts_total[5m])"
}]
},
{
"title": "Users by Proxy Header",
"targets": [{
"expr": "count by (user) (mcp_authenticated_requests_total)"
}]
}
]
}
}
Best Practices¶
Production Checklist
- Network isolation between proxy and gateway
- TLS encryption for all connections
- Rate limiting at proxy level
- Audit logging enabled
- Monitoring and alerting configured
- Backup authentication method available
- Documentation updated
- Security review completed