Security Best Practices

This guide outlines security best practices when using oauth-mcp-proxy in production.


Breaking Changes (Security Hardening v1.1.0)

The following security improvements introduce breaking changes:

1. Issuer URL Validation (CRITICAL)

What changed: OIDC providers (Okta, Google, Azure) now enforce HTTPS validation for issuer URLs in Config.Validate().

Why: Prevents man-in-the-middle attacks on OAuth communication.

Impact: Invalid issuer URLs will cause NewServer() to fail with error.

Migration:

// ✅ Valid configurations
Issuer: "https://company.okta.com"              // Production
Issuer: "http://localhost:8080"                 // Local testing only
Issuer: "http://127.0.0.1:8080"                 // Local testing only

// ❌ Invalid - will fail validation
Issuer: "http://company.okta.com"               // Must use HTTPS
Issuer: "company.okta.com"                      // Missing scheme
Issuer: "https://192.168.1.1/issuer"            // IP addresses not allowed

2. State Signing Key Initialization

What changed: NewServer() now panics if the state signing key cannot be generated via crypto/rand.

Why: Prevents weak fallback that could allow state forgery attacks.

Impact: Server startup will fail immediately if crypto/rand fails.

Migration: No code changes needed. Ensure your system has a working CSPRNG (crypto/rand). This should never fail on healthy systems.

3. Nonce Generation Failure Behavior

What changed: generateSecureNonce() now panics instead of falling back to weak timestamp-based nonces.

Why: Timestamp-based nonces are predictable and vulnerable to replay attacks.

Impact: OAuth authorization requests will fail if crypto/rand fails.

Migration: No code changes needed. Ensure your system has a working CSPRNG.

4. CreateRequestAuthHook Now Rejects Requests

What changed: CreateRequestAuthHook() now returns an error for all requests instead of silently allowing them through.

Why: The previous implementation returned nil (allow-all), which created a security bypass if integrators relied on this hook for authentication. The hook's signature cannot propagate context changes, making it fundamentally unable to perform real auth.

Impact: Any code using CreateRequestAuthHook() will now reject all requests with an error.

Migration: Switch to WithOAuth() tool-level middleware, which properly handles authentication and context propagation:

5. Redirect URI Validation in Config

What changed: Config.Validate() now validates redirect URIs and fixed redirect URIs at startup. HTTPS is required for non-localhost URIs, fragments are rejected, and whitespace-only URI lists are caught.

Why: Prevents open redirect vulnerabilities and ensures OAuth 2.0 spec compliance.

Impact: Existing configs with HTTP redirect URIs for non-localhost hosts, or URIs containing fragments, will fail validation at startup.

Migration:

6. Error Message Simplification

What changed: Security-sensitive error paths now return generic error messages to prevent information leakage.

Why: Prevents attackers from learning internal system details through error messages.

Impact: Debugging authentication failures from client-side may be less informative.

Migration: Use server logs for detailed debugging. Client-facing errors are intentionally generic for security.

Backward-Compatible Changes

The following security improvements are fully backward-compatible:

  • Token cache expiry fix - Cache now respects JWT expiration times

  • State replay protection - Legacy states without timestamp/nonce still accepted for rolling deploys

  • Input validation - Only affects malformed/abusive requests

  • Query injection prevention - Transparent fix, no API changes

  • go-sdk adapter session management - Fully backwards compatible



🔒 Secrets Management

Never Commit Secrets

❌ BAD:

✅ GOOD:

Environment Variables

Load with library like godotenv:

.gitignore


🔐 JWT Secret Strength (HMAC Provider)

Minimum Requirements

Validation

Rotation

  • Rotate every: 90 days recommended

  • Process: Generate new secret → Update config → Deploy → Update token generators

  • Zero downtime: Temporarily accept both old and new secrets during rotation


🌐 HTTPS in Production

Always Use TLS

❌ NEVER in production:

✅ Production:

Get Certificates

Development:

Production:

Certificate Management


🎯 Audience Validation

Why Audience Matters

Prevents token reuse across services:

Token for Service A cannot be used on Service B (even with same issuer).

Configuration

HMAC Provider:

OIDC Providers:

  • Okta: Configure custom audience in auth server claims

  • Google: Use Client ID as audience

  • Azure: Use Application ID or custom App ID URI

Validation


🔄 Token Caching & Expiration

Cache Behavior

  • Cache TTL: 5 minutes (hardcoded in v0.1.0)

  • Cache scope: Per Server instance

  • Cache key: SHA-256 hash of token

Token Expiration Recommendations

User tokens:

  • Short-lived: 1 hour

  • Refresh tokens: 7-30 days

  • Reason: Limits damage if compromised

Service tokens:

  • Medium-lived: 6-24 hours

  • Reason: Balance between security and token refresh overhead


🛡️ PKCE (Proof Key for Code Exchange)

Automatic Protection

oauth-mcp-proxy automatically supports PKCE (RFC 7636):

  • Prevents authorization code interception attacks

  • Required for public clients (mobile, desktop, browser)

  • Automatically validated when code_challenge provided

No Configuration Needed

PKCE is automatically enabled when client provides:

  • code_challenge parameter in /oauth/authorize

  • code_verifier parameter in /oauth/token


🚪 Redirect URI Security

Native Mode (Client OAuth)

Localhost only for security:

Proxy Mode (Server OAuth)

Allowlist configuration:

Security checks:

  • HTTPS required for non-localhost

  • No fragment allowed (per OAuth 2.0 spec)

  • Exact match validation (no wildcards)


🎫 Token Security

Token Storage (Client Side)

Browser:

  • Use httpOnly cookies or sessionStorage (NOT localStorage)

  • Clear on logout

Mobile/Desktop:

  • Use OS keychain (macOS Keychain, Windows Credential Manager)

  • Never store in plain text files

CLI Tools:

  • Store in encrypted config files

  • Use OS-specific secure storage when possible

Token Transmission

Always use Authorization header:

Never:

  • In URL query parameters (logged in web servers)

  • In cookies without httpOnly flag

  • In localStorage (XSS vulnerable)


🔍 Logging & Monitoring

What Gets Logged

oauth-mcp-proxy logs (with custom logger or default):

Info Level:

  • Authorization requests

  • Successful authentications

  • Token cache hits

Warn Level:

  • Security violations (invalid redirects)

  • Configuration issues

Error Level:

  • Token validation failures

  • OAuth provider errors

What NOT to Log

Safe: Token hash (SHA-256)

NEVER log: Full tokens

Custom Logger for Production


🚨 Rate Limiting

Built-in Rate Limiter

oauth-mcp-proxy includes a built-in rate limiter:

Features:

  • Fixed-window rate limiting

  • Automatic cleanup of expired entries

  • Thread-safe (uses sync.RWMutex)

  • Background cleanup goroutine support

Additional Protection

For OAuth endpoints, consider additional rate limiting:


🔁 Security Headers

OAuth handler automatically adds security headers:


🛡️ Built-in Security Features

oauth-mcp-proxy includes multiple security defenses:

State Replay Protection

OAuth state parameters are protected against replay attacks:

  • Timestamp validation - States expire after 10 minutes

  • Nonce uniqueness - Each state uses a cryptographically random nonce

  • Replay detection - Nonce tracked and rejected if reused

  • Automatic cleanup - Expired nonces removed to prevent memory leaks

  • Rolling deploy compatible - Accepts states from older versions during upgrades

Token Cache Security

Token caching respects JWT expiration times:

Input Validation

Request parameters are validated to prevent abuse:

  • code parameter - Max 512 characters

  • state parameter - Max 256 characters

  • code_challenge parameter - Max 256 characters

  • Request body size - Limited to prevent DoS (1MB for /oauth/token, 256KB for /oauth/register)

Issuer URL Validation

OIDC provider issuer URLs are validated:

  • HTTPS required for non-localhost URLs (prevents MITM attacks)

  • Valid URL format - Must parse correctly

  • Not empty - Issuer must be specified

  • No raw IP addresses - Hostnames only (prevents misconfiguration)

Constant-Time Cryptography

HMAC signatures verified using constant-time comparison:

Secure Random Number Generation

Nonces generated using crypto/rand:

  • Panics on failure - No fallback to weak timestamp-based nonces

  • Cryptographically secure - Uses system CSPRNG

Session Management (Official SDK)

The official SDK adapter populates the go-sdk auth context:

  • auth.TokenInfo populated - User ID and expiration set for session binding

  • Session hijacking prevention - Requests from different users rejected

  • CORS support - OPTIONS requests pass through for browser clients

Add application-level headers:


📋 Security Checklist

Pre-Production

Configuration:

Built-in Security (already enabled):

Optional:

Regular Maintenance


🚩 Security Incidents

Token Compromise

If JWT secret (HMAC) leaked:

  1. Generate new secret immediately

  2. Update config and redeploy

  3. All existing tokens invalidated (users must re-auth)

  4. Review logs for suspicious activity

If client secret (OIDC) leaked:

  1. Revoke in OAuth provider (Okta/Google/Azure)

  2. Generate new secret

  3. Update config and redeploy

  4. Existing user tokens still valid (not affected)

Suspicious Activity

  • Multiple failed auth attempts → Consider IP blocking

  • Unusual token usage patterns → Review logs

  • Invalid redirect URI attempts → Security violation logged


📚 Additional Resources


🤝 Reporting Security Issues

Found a security vulnerability? Email security@[your-domain] or open a confidential GitHub Security Advisory.

Do NOT open public GitHub issues for security vulnerabilities.

Last updated

Was this helpful?