Overview
Secure MCP Gateway provides comprehensive OAuth 2.0 and 2.1 support for authenticating with remote MCP servers. This includes client credentials flow, mutual TLS (mTLS), automatic token management, and secure token caching.
Supported Features
OAuth 2.0 & 2.1 Full specification compliance with security best practices
Client Credentials Server-to-server authentication flow
Mutual TLS (mTLS) Enhanced security with client certificates (RFC 8705)
Token Caching Automatic caching with proactive refresh
Token Revocation RFC 7009 compliant token revocation
Scope Validation Verifies returned tokens have requested scopes
Retry Logic Exponential backoff for transient failures
Metrics & Monitoring Comprehensive tracking of OAuth operations
Quick Start
Basic OAuth 2.1 Setup
Obtain OAuth Credentials
Get your client ID and secret from your OAuth provider (Auth0, Okta, Keycloak, etc.)
Add Server with OAuth
secure-mcp-gateway config add-server \
--config-name "default_config" \
--server-name "oauth-api" \
--server-command "npx" \
--args= "-y,mcp-remote,https://api.example.com/mcp" \
--description "OAuth-protected remote server"
Configure OAuth
Edit ~/.enkrypt/enkrypt_mcp_config.json and add OAuth config: {
"server_name" : "oauth-api" ,
"oauth_config" : {
"enabled" : true ,
"is_remote" : true ,
"OAUTH_VERSION" : "2.1" ,
"OAUTH_CLIENT_ID" : "your-client-id" ,
"OAUTH_CLIENT_SECRET" : "your-client-secret" ,
"OAUTH_TOKEN_URL" : "https://auth.example.com/oauth/token" ,
"OAUTH_AUDIENCE" : "https://api.example.com"
}
}
Test Connection
Restart Claude Desktop and test: List all servers and discover tools from oauth-api
Configuration Reference
Minimal Configuration
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_VERSION" : "2.1" ,
"OAUTH_GRANT_TYPE" : "client_credentials" ,
"OAUTH_CLIENT_ID" : "your-client-id" ,
"OAUTH_CLIENT_SECRET" : "your-client-secret" ,
"OAUTH_TOKEN_URL" : "https://auth.example.com/oauth/token"
}
}
Complete Configuration
{
"oauth_config" : {
// Required
"enabled" : true ,
"OAUTH_VERSION" : "2.1" ,
"OAUTH_GRANT_TYPE" : "client_credentials" ,
"OAUTH_CLIENT_ID" : "your-client-id" ,
"OAUTH_CLIENT_SECRET" : "your-client-secret" ,
"OAUTH_TOKEN_URL" : "https://auth.example.com/oauth/token" ,
// Server detection
"is_remote" : true ,
// Token scope and audience
"OAUTH_AUDIENCE" : "https://api.example.com" ,
"OAUTH_ORGANIZATION" : "org-123" ,
"OAUTH_SCOPE" : "read write delete" ,
"OAUTH_RESOURCE" : "https://resource.example.com" ,
// Security settings
"OAUTH_USE_BASIC_AUTH" : true ,
"OAUTH_ENFORCE_HTTPS" : true ,
"OAUTH_TOKEN_IN_HEADER_ONLY" : true ,
"OAUTH_VALIDATE_SCOPES" : true ,
// Token management
"OAUTH_TOKEN_EXPIRY_BUFFER" : 300 ,
// Mutual TLS (optional)
"OAUTH_USE_MTLS" : false ,
"OAUTH_CLIENT_CERT_PATH" : "~/.certs/client.pem" ,
"OAUTH_CLIENT_KEY_PATH" : "~/.certs/client-key.pem" ,
"OAUTH_CA_BUNDLE_PATH" : "~/.certs/ca-bundle.pem" ,
// Token revocation
"OAUTH_REVOCATION_URL" : "https://auth.example.com/oauth/revoke" ,
// Advanced
"OAUTH_ADDITIONAL_PARAMS" : {},
"OAUTH_CUSTOM_HEADERS" : {}
}
}
Configuration Fields
Core Settings
Field Type Required Default Description enabledboolean Yes falseEnable OAuth for this server OAUTH_VERSIONstring No "2.0"OAuth version: "2.0" or "2.1" OAUTH_GRANT_TYPEstring No "client_credentials"Only client_credentials supported OAUTH_CLIENT_IDstring Yes* - OAuth client ID OAUTH_CLIENT_SECRETstring Yes* - OAuth client secret OAUTH_TOKEN_URLstring Yes - Token endpoint (HTTPS required for 2.1)
*Required when enabled: true
Server Detection
Field Type Description is_remoteboolean Explicitly mark as remote server (recommended)
The gateway auto-detects remote servers by checking for npx, mcp-remote, curl, or http(s):// in command/args. Set is_remote: true explicitly to avoid false detection.
Scope & Audience
Field Type Description OAUTH_AUDIENCEstring Intended audience for the token OAUTH_ORGANIZATIONstring Organization identifier OAUTH_SCOPEstring Space-separated scopes (e.g., "read write") OAUTH_RESOURCEstring Resource Indicator (OAuth 2.1, RFC 8707)
Security Settings
Field Type Default Description OAUTH_USE_BASIC_AUTHboolean trueUse HTTP Basic Auth (client_secret_basic) OAUTH_ENFORCE_HTTPSboolean trueRequire HTTPS for token URL OAUTH_TOKEN_IN_HEADER_ONLYboolean trueNever use query params for tokens OAUTH_VALIDATE_SCOPESboolean trueValidate returned token has requested scopes
OAuth 2.1 Compliance
Requires OAUTH_ENFORCE_HTTPS: true
Recommends OAUTH_USE_BASIC_AUTH: true
Enforces OAUTH_TOKEN_IN_HEADER_ONLY: true
Token Management
Field Type Default Description OAUTH_TOKEN_EXPIRY_BUFFERinteger 300Seconds before expiry to refresh token
How Token Refresh Works:
Token Acquired
Token obtained with expires_in: 3600 (1 hour)
Cached with Expiry
Cached with expires_at timestamp
Proactive Refresh
Refreshed when expires_at - 300s < now (5 min before expiry)
Automatic Retry
On failure, retry with exponential backoff (2s, 4s, 8s)
Mutual TLS (mTLS)
What is mTLS?
Mutual TLS provides enhanced security by requiring both client and server to present certificates during the TLS handshake. This is specified in RFC 8705 .
Setting Up mTLS
Generate Client Certificate
Generate a client certificate and private key: # Generate private key
openssl genrsa -out client-key.pem 2048
# Generate certificate signing request
openssl req -new -key client-key.pem -out client.csr
# Generate self-signed certificate (or get signed by CA)
openssl x509 -req -days 365 -in client.csr \
-signkey client-key.pem -out client.pem
# Set permissions
chmod 600 client-key.pem client.pem
Upload Certificate to OAuth Provider
Register your client certificate with your OAuth provider (Auth0, Okta, etc.)
Configure Gateway
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_USE_MTLS" : true ,
"OAUTH_CLIENT_CERT_PATH" : "~/.certs/client.pem" ,
"OAUTH_CLIENT_KEY_PATH" : "~/.certs/client-key.pem" ,
"OAUTH_CA_BUNDLE_PATH" : "~/.certs/ca-bundle.pem" ,
"OAUTH_CLIENT_ID" : "your-client-id" ,
"OAUTH_CLIENT_SECRET" : "your-client-secret" ,
"OAUTH_TOKEN_URL" : "https://auth.example.com/oauth/token"
}
}
Test Connection
The gateway will automatically use the certificate for all OAuth requests.
Certificate Security
Store certificates in secure directory with chmod 600
Never commit certificates to version control
Rotate certificates before expiry
Use different certificates for different environments
Token Injection
For Remote Servers
Tokens are automatically injected as HTTP headers:
npx -y mcp-remote https://api.example.com/mcp \
--header "Authorization: Bearer eyJhbGci..."
The gateway handles this automatically when is_remote: true.
For Local Servers
Tokens are injected as environment variables:
ENKRYPT_ACCESS_TOKEN = eyJhbGci...
AUTHORIZATION = Bearer eyJhbGci...
OAUTH_ACCESS_TOKEN = eyJhbGci...
OAUTH_TOKEN_TYPE = Bearer
HTTP_HEADER_Authorization = Bearer eyJhbGci...
HTTP_HEADER_AUTHORIZATION = Bearer eyJhbGci...
Your MCP server can read these variables to authenticate with remote APIs.
Provider-Specific Examples
Auth0
Configuration
Getting Credentials
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_VERSION" : "2.1" ,
"OAUTH_GRANT_TYPE" : "client_credentials" ,
"OAUTH_CLIENT_ID" : "your-auth0-client-id" ,
"OAUTH_CLIENT_SECRET" : "your-auth0-client-secret" ,
"OAUTH_TOKEN_URL" : "https://yourtenant.auth0.com/oauth/token" ,
"OAUTH_AUDIENCE" : "https://yourapi.example.com" ,
"OAUTH_SCOPE" : "read:data write:data"
}
}
Okta
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_VERSION" : "2.0" ,
"OAUTH_CLIENT_ID" : "okta-client-id" ,
"OAUTH_CLIENT_SECRET" : "okta-client-secret" ,
"OAUTH_TOKEN_URL" : "https://dev-123456.okta.com/oauth2/default/v1/token" ,
"OAUTH_REVOCATION_URL" : "https://dev-123456.okta.com/oauth2/default/v1/revoke" ,
"OAUTH_AUDIENCE" : "api://default" ,
"OAUTH_SCOPE" : "custom.scope"
}
}
Keycloak
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_VERSION" : "2.0" ,
"OAUTH_CLIENT_ID" : "keycloak-client" ,
"OAUTH_CLIENT_SECRET" : "keycloak-secret" ,
"OAUTH_TOKEN_URL" : "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" ,
"OAUTH_AUDIENCE" : "account" ,
"OAUTH_SCOPE" : "openid profile email" ,
"OAUTH_ADDITIONAL_PARAMS" : {
"realm" : "myrealm"
}
}
}
GitHub Apps (mTLS)
{
"oauth_config" : {
"enabled" : true ,
"OAUTH_VERSION" : "2.0" ,
"OAUTH_CLIENT_ID" : "github-app-client-id" ,
"OAUTH_CLIENT_SECRET" : "github-app-client-secret" ,
"OAUTH_TOKEN_URL" : "https://github.com/login/oauth/access_token" ,
"OAUTH_USE_MTLS" : true ,
"OAUTH_CLIENT_CERT_PATH" : "~/.ssh/github-app.pem" ,
"OAUTH_CLIENT_KEY_PATH" : "~/.ssh/github-app-key.pem"
}
}
Monitoring & Troubleshooting
Enable Debug Logging
{
"common_mcp_gateway_config" : {
"enkrypt_log_level" : "DEBUG"
}
}
Logs will show:
[OAuthService] Token request correlation_id=f47ac10b for oauth-api
[OAuthService] Successfully obtained token, expires in 3600s
[OAuthService] Token cached for oauth-api (config: abc-123)
Common Errors
OAuth token request failed: invalid_client
Solution: Verify OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET are correct
OAuth 2.1 requires HTTPS for token_url
Solution: Use https:// URL or set OAUTH_VERSION: "2.0"
Client certificate not found: /path/to/cert.pem
Solution: Verify certificate path and file permissions (chmod 600)
Token scopes validation failed. Requested: read write, Received: read
Solution: Request correct scopes or set OAUTH_VALIDATE_SCOPES: false
Network Errors (Auto-Retry)
Retrying token acquisition for oauth-api, attempt 2/3
Info: Automatic retry with exponential backoff (2s, 4s, 8s)
Viewing Metrics
OAuth metrics are available programmatically:
from secure_mcp_gateway.services.oauth import get_oauth_service
oauth_service = get_oauth_service()
metrics = oauth_service.get_metrics()
print (metrics)
# {
# "token_acquisitions_total": 150,
# "token_acquisitions_success": 148,
# "token_acquisitions_failure": 2,
# "token_cache_hits": 1200,
# "token_cache_misses": 150,
# "token_refreshes": 45,
# "cache_hit_ratio": 0.889,
# "success_rate": 0.987,
# "avg_latency_ms": 234.5,
# "active_tokens": 12
# }
Security Best Practices
Use OAuth 2.1 OAuth 2.1 has stricter security requirements and removes deprecated features
Enable HTTPS Always use HTTPS for token URLs in production
Use Basic Auth Prefer client_secret_basic over client_secret_post
Validate Scopes Always verify returned tokens have requested scopes
Enable mTLS Use mutual TLS for high-security environments
Rotate Secrets Regularly rotate client secrets and certificates
Minimal Scopes Request only the scopes you need (principle of least privilege)
Secure Storage Store certificates with proper permissions (chmod 600)
Security Checklist
HTTPS Enforced
OAUTH_ENFORCE_HTTPS: true for production
Scope Validation Enabled
OAUTH_VALIDATE_SCOPES: true
Credentials Not Committed
Add .enkrypt/ to .gitignore
Certificates Secured
chmod 600 on all certificate files
Different Creds per Environment
Separate client IDs for dev/staging/prod
Monitoring Enabled
Track OAuth metrics and failures
Advanced Topics
Token Revocation
Revoke tokens programmatically:
from secure_mcp_gateway.services.oauth import get_oauth_service
oauth_service = get_oauth_service()
success, error = await oauth_service.revoke_token(
server_name = "oauth-api" ,
token = "access_token_to_revoke" ,
oauth_config = config,
token_type_hint = "access_token"
)
Force Token Refresh
from secure_mcp_gateway.services.oauth import refresh_server_oauth_token
token, error = await refresh_server_oauth_token(
server_name = "oauth-api" ,
server_entry = server_config,
config_id = "config-id"
)
Invalidate Cached Token
from secure_mcp_gateway.services.oauth import invalidate_server_oauth_token
await invalidate_server_oauth_token( "oauth-api" , "config-id" )
FAQ
Can I use authorization code grant?
Not yet. Only client credentials grant is currently supported. Authorization code grant will be added in a future release.
How do I know if my server is remote?
Set is_remote: true explicitly in oauth_config. Auto-detection checks for npx, mcp-remote, curl, or http(s):// in command/args.
Do tokens work with stdio MCP servers?
Yes! Tokens are injected as environment variables for stdio servers.
How often are tokens refreshed?
Proactively, 5 minutes (300s) before expiry by default. Configurable via OAUTH_TOKEN_EXPIRY_BUFFER.
Can I use OAuth without mTLS?
Yes. mTLS is optional and disabled by default.
What happens if token acquisition fails?
Automatic retry with exponential backoff (3 attempts: 2s, 4s, 8s). After exhaustion, returns error.
Are tokens shared across servers?
No. Each server has its own cached token, keyed by server_name and config_id.
Next Steps
Add MCP Servers Learn how to add and configure MCP servers
Configure Guardrails Add security layers to OAuth-protected servers
External Cache Improve token caching with Redis/KeyDB
Custom Plugins Create custom auth providers