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 allowed2. 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:
Use mkcert for local testing
Production:
Use Let's Encrypt with certbot
Or your cloud provider's certificate service (AWS ACM, GCP Certificate Manager)
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_challengeparameter in /oauth/authorizecode_verifierparameter 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
httpOnlycookies 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:
Generate new secret immediately
Update config and redeploy
All existing tokens invalidated (users must re-auth)
Review logs for suspicious activity
If client secret (OIDC) leaked:
Revoke in OAuth provider (Okta/Google/Azure)
Generate new secret
Update config and redeploy
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?