Documentation
¶
Overview ¶
Package server provides OAuth 2.1 authorization server implementation with MCP support
Package server implements the core OAuth 2.1 server logic.
This package provides the OAuth authorization server implementation with support for the authorization code flow, PKCE, token refresh, and client registration. It coordinates between OAuth providers, storage backends, and security features while remaining provider-agnostic.
Two-Layer PKCE Architecture ¶
The server implements OAuth 2.1 PKCE (Proof Key for Code Exchange) at two layers:
Layer 1 (MCP Client → OAuth Server): - Client-provided PKCE challenge and verifier - Protects public clients (mobile apps, SPAs, CLI tools) - Prevents authorization code interception attacks Layer 2 (OAuth Server → Provider): - Server-generated PKCE challenge and verifier - Protects against authorization code injection attacks - OAuth 2.1 defense-in-depth for confidential clients - Works alongside client_secret authentication
This dual-layer approach provides comprehensive security even if one layer is compromised. See SECURITY_ARCHITECTURE.md for detailed security model.
The Server type delegates to specialized modules:
- Provider integration (providers package)
- Token and client storage (storage package)
- Security features (security package)
Key Features:
- OAuth 2.1 compliance with mandatory PKCE
- Two-layer PKCE (client-to-server and server-to-provider)
- Refresh token rotation with reuse detection
- Dynamic client registration (RFC 7591)
- Comprehensive security auditing
- Rate limiting (IP and user-based)
- Token encryption at rest
Example usage:
provider := google.NewProvider(clientID, clientSecret, redirectURL)
store := memory.NewStore()
config := &server.Config{
Issuer: "https://auth.example.com",
RequirePKCE: true,
}
srv, err := server.New(provider, store, store, store, config, logger)
if err != nil {
log.Fatal(err)
}
Index ¶
- Constants
- Variables
- func ExtractIDToken(token *oauth2.Token) string
- func GetRedirectURIErrorCategory(err error) string
- func IsRedirectURISecurityError(err error) bool
- type CORSConfig
- type ClientMetadata
- type Clock
- type Config
- func (c *Config) AuthorizationEndpoint() string
- func (c *Config) GetResourceIdentifier() string
- func (c *Config) IntrospectionEndpoint() string
- func (c *Config) ProtectedResourceMetadataEndpoint() string
- func (c *Config) RegistrationEndpoint() string
- func (c *Config) RevocationEndpoint() string
- func (c *Config) SetTrustedSchemesMap(schemes []string)
- func (c *Config) TokenEndpoint() string
- type DNSResolver
- type InstrumentationConfig
- type InterstitialBranding
- type InterstitialConfig
- type ProtectedResourceConfig
- type RedirectURISecurityError
- type Server
- func (s *Server) CanRegisterWithTrustedScheme(redirectURIs []string) (allowed bool, scheme string, err error)
- func (s *Server) ExchangeAuthorizationCode(ctx context.Context, ...) (*oauth2.Token, string, error)
- func (s *Server) GetClient(ctx context.Context, clientID string) (*storage.Client, error)
- func (s *Server) HandleProviderCallback(ctx context.Context, providerState, code string) (*storage.AuthorizationCode, string, error)
- func (s *Server) RefreshAccessToken(ctx context.Context, refreshToken, clientID string) (*oauth2.Token, error)
- func (s *Server) RegisterClient(ctx context.Context, clientName, clientType, tokenEndpointAuthMethod string, ...) (*storage.Client, string, error)
- func (s *Server) RevokeAllTokensForUserClient(ctx context.Context, userID, clientID string) error
- func (s *Server) RevokeToken(ctx context.Context, token, clientID, clientIP string) error
- func (s *Server) SetAuditor(aud *security.Auditor)
- func (s *Server) SetClientRegistrationRateLimiter(rl *security.ClientRegistrationRateLimiter)
- func (s *Server) SetEncryptor(enc *security.Encryptor)
- func (s *Server) SetInstrumentation(inst *instrumentation.Instrumentation)
- func (s *Server) SetMetadataFetchRateLimiter(rl *security.RateLimiter)
- func (s *Server) SetRateLimiter(rl *security.RateLimiter)
- func (s *Server) SetSecurityEventRateLimiter(rl *security.RateLimiter)
- func (s *Server) SetSessionCreationHandler(handler SessionCreationHandler)
- func (s *Server) SetSessionRevocationHandler(handler SessionRevocationHandler)
- func (s *Server) SetTokenFamilyRevocationHandler(handler TokenFamilyRevocationHandler)deprecated
- func (s *Server) SetTokenRefreshHandler(handler TokenRefreshHandler)
- func (s *Server) SetUserRateLimiter(rl *security.RateLimiter)
- func (s *Server) Shutdown(ctx context.Context) error
- func (s *Server) ShutdownWithTimeout(timeout time.Duration) error
- func (s *Server) StartAuthorizationFlow(ctx context.Context, ...) (string, error)
- func (s *Server) TokenStore() storage.TokenStore
- func (s *Server) ValidateClientCredentials(ctx context.Context, clientID, clientSecret string) error
- func (s *Server) ValidateRedirectURIAtAuthorizationTime(ctx context.Context, redirectURI string) error
- func (s *Server) ValidateRedirectURIForRegistration(ctx context.Context, redirectURI string) error
- func (s *Server) ValidateRedirectURIsForRegistration(ctx context.Context, redirectURIs []string) error
- func (s *Server) ValidateToken(ctx context.Context, accessToken string) (*providers.UserInfo, error)
- type SessionCreationHandler
- type SessionRevocationHandler
- type TokenFamilyRevocationHandlerdeprecated
- type TokenRefreshHandler
Constants ¶
const ( // ClientTypeConfidential represents a confidential OAuth client ClientTypeConfidential = "confidential" // ClientTypePublic represents a public OAuth client ClientTypePublic = "public" )
Client type constants (also defined in root package constants.go) These are duplicated to avoid import cycles since root package imports server package
const ( // TokenEndpointAuthMethodNone represents no authentication (public clients) TokenEndpointAuthMethodNone = "none" // TokenEndpointAuthMethodBasic represents HTTP Basic authentication TokenEndpointAuthMethodBasic = "client_secret_basic" // TokenEndpointAuthMethodPost represents POST form parameters TokenEndpointAuthMethodPost = "client_secret_post" )
Token endpoint authentication method constants (RFC 7591) These are duplicated to avoid import cycles since root package imports server package
const ( SchemeHTTP = "http" SchemeHTTPS = "https" )
URI scheme constants (shared with validation.go)
const ( // EndpointPathAuthorize is the authorization endpoint path EndpointPathAuthorize = "/oauth/authorize" // EndpointPathToken is the token endpoint path EndpointPathToken = "/oauth/token" // #nosec G101 -- This is a URL path, not a credential // EndpointPathRegister is the dynamic client registration endpoint path (RFC 7591) EndpointPathRegister = "/oauth/register" // EndpointPathRevoke is the token revocation endpoint path (RFC 7009) EndpointPathRevoke = "/oauth/revoke" // EndpointPathIntrospect is the token introspection endpoint path (RFC 7662) EndpointPathIntrospect = "/oauth/introspect" // EndpointPathProtectedResourceMetadata is the Protected Resource Metadata discovery path (RFC 9728) EndpointPathProtectedResourceMetadata = "/.well-known/oauth-protected-resource" )
OAuth endpoint paths
const ( // MinProviderTokenTTL is the minimum allowed value for ProviderTokenTTL (1 hour). // Shorter values would defeat the purpose of extending token storage for SSO forwarding. MinProviderTokenTTL = 3600 // 1 hour // MaxProviderTokenTTL is the recommended maximum for ProviderTokenTTL (7 days). // Values exceeding this may lead to stale tokens accumulating in storage. MaxProviderTokenTTL = 604800 // 7 days // DefaultProviderTokenTTL is the default value for ProviderTokenTTL (24 hours). DefaultProviderTokenTTL = 86400 // 24 hours )
ProviderTokenTTL validation constants
const ( ErrorCodeInvalidClient = "invalid_client" ErrorCodeInvalidRequest = "invalid_request" ErrorCodeInvalidRedirectURI = "invalid_redirect_uri" ErrorCodeInvalidScope = "invalid_scope" ErrorCodeInvalidGrant = "invalid_grant" )
OAuth 2.0 error codes from RFC 6749. Note: These are intentionally duplicated from errors.go to avoid circular imports (root package imports server for type aliases, server can't import root). Keep these in sync with errors.go.
const ( // RedirectURIStageRegistration indicates validation during client registration. RedirectURIStageRegistration = "registration" // RedirectURIStageAuthorization indicates validation during authorization request. RedirectURIStageAuthorization = "authorization" )
Redirect URI validation stage constants for metrics.
const ( RedirectURIErrorCategoryBlockedScheme = "blocked_scheme" RedirectURIErrorCategoryPrivateIP = "private_ip" RedirectURIErrorCategoryLinkLocal = "link_local" RedirectURIErrorCategoryLoopback = "loopback_not_allowed" RedirectURIErrorCategoryHTTPNotAllowed = "http_not_allowed" RedirectURIErrorCategoryDNSPrivateIP = "dns_resolves_to_private_ip" RedirectURIErrorCategoryDNSLinkLocal = "dns_resolves_to_link_local" RedirectURIErrorCategoryDNSFailure = "dns_resolution_failed" RedirectURIErrorCategoryInvalidFormat = "invalid_format" RedirectURIErrorCategoryFragment = "fragment_not_allowed" RedirectURIErrorCategoryUnspecifiedAddr = "unspecified_address" )
Redirect URI security error categories for metrics and logging.
const ( MinCodeVerifierLength = 43 MaxCodeVerifierLength = 128 PKCEMethodS256 = "S256" PKCEMethodPlain = "plain" )
PKCE validation constants (RFC 7636)
const DefaultMaxNegativeEntries = 500
DefaultMaxNegativeEntries is the default maximum number of negative cache entries
const DefaultNegativeCacheTTL = 5 * time.Minute
DefaultNegativeCacheTTL is the default TTL for negative cache entries SECURITY: Shorter than positive entries to allow retries after fixes
const ( // MaxResourceLength is the maximum allowed length for resource parameter // RFC 3986 suggests 2048 characters as a reasonable URI length limit MaxResourceLength = 2048 )
Resource parameter validation constants (RFC 8707)
const ( // MinTokenBytes is the minimum number of random bytes required for secure tokens. // 32 bytes = 256 bits of entropy, which exceeds NIST recommendations for // cryptographic keys and is sufficient to prevent brute-force attacks. // Base64url encoding without padding produces 43 characters from 32 bytes. MinTokenBytes = 32 )
const OAuthSpecVersion = "OAuth 2.1"
OAuthSpecVersion is the OAuth specification version this library implements. Note: This is intentionally duplicated from constants.go to avoid circular imports. Keep in sync with constants.go.
Variables ¶
var ( // AllowedHTTPSchemes lists allowed HTTP-based redirect URI schemes AllowedHTTPSchemes = []string{SchemeHTTP, SchemeHTTPS} // DefaultBlockedRedirectSchemes is the canonical list of URI schemes that are blocked // for redirect URIs. These schemes can be used for XSS attacks (javascript:, data:, blob:) // or local file/app access (file:, ms-appx:). This is the single source of truth for blocked schemes. // Used by Config.BlockedRedirectSchemes default and validateCustomScheme. // // Blocked schemes: // - javascript: XSS attacks via script execution // - data: XSS attacks via inline content // - file: Local filesystem access // - vbscript: Legacy XSS (IE) // - about: Browser internals access // - ftp: Insecure protocol // - blob: XSS via Blob URLs (browser exploit vector) // - ms-appx: Windows app package access // - ms-appx-web: Windows app web content access DefaultBlockedRedirectSchemes = []string{ "javascript", "data", "file", "vbscript", "about", "ftp", "blob", "ms-appx", "ms-appx-web", } // DangerousSchemes is an alias for backward compatibility. // // Deprecated: Use DefaultBlockedRedirectSchemes instead. DangerousSchemes = DefaultBlockedRedirectSchemes // DefaultRFC3986SchemePattern is the default regex pattern for custom URI schemes (RFC 3986) DefaultRFC3986SchemePattern = []string{"^[a-z][a-z0-9+.-]*$"} )
Functions ¶
func ExtractIDToken ¶ added in v0.2.48
ExtractIDToken extracts the id_token string from an oauth2.Token's Extra field. Returns empty string if the token is nil, id_token is not present, or not a valid string. Per OpenID Connect Core 1.0 Section 3.1.3.3, the id_token is REQUIRED in token responses for OIDC flows, enabling silent re-authentication with id_token_hint.
func GetRedirectURIErrorCategory ¶ added in v0.2.18
GetRedirectURIErrorCategory returns the error category if the error is a RedirectURISecurityError. Uses errors.As to properly handle wrapped errors.
func IsRedirectURISecurityError ¶ added in v0.2.18
IsRedirectURISecurityError checks if an error is a redirect URI security validation error. Uses errors.As to properly handle wrapped errors.
Types ¶
type CORSConfig ¶ added in v0.1.22
type CORSConfig struct {
// AllowedOrigins is a list of allowed origin URLs for CORS requests.
// Examples: ["https://app.example.com", "https://dashboard.example.com"]
// Use "*" to allow all origins (requires AllowWildcardOrigin=true).
// Empty list means CORS is disabled (default, secure).
AllowedOrigins []string
// AllowWildcardOrigin explicitly enables wildcard (*) origin support.
// WARNING: This allows ANY website to make cross-origin requests to your OAuth server.
// This creates significant CSRF attack surface and is NOT RECOMMENDED for production.
// Only enable for development or when you fully understand the security implications.
// Must be explicitly set to true when using "*" in AllowedOrigins.
// Default: false (wildcard origins are rejected)
AllowWildcardOrigin bool
// AllowCredentials enables the Access-Control-Allow-Credentials header.
// Required if your browser client needs to send cookies or authorization headers.
// Must be true for OAuth flows that require Bearer tokens.
// SECURITY: Cannot be used with wildcard origin (per CORS specification).
// Default: false
AllowCredentials bool
// MaxAge is the maximum time (in seconds) browsers can cache preflight responses.
// Default: 3600 (1 hour)
MaxAge int
}
CORSConfig holds CORS (Cross-Origin Resource Sharing) configuration for browser-based clients CORS is disabled by default for security. Only enable for browser-based MCP clients.
type ClientMetadata ¶ added in v0.1.36
type ClientMetadata struct {
ClientID string `json:"client_id"`
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Scope string `json:"scope,omitempty"`
Contacts []string `json:"contacts,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
}
ClientMetadata represents OAuth client metadata fetched from a URL-based client_id Implements draft-ietf-oauth-client-id-metadata-document-00
type Clock ¶ added in v0.1.52
Clock interface for time operations, allowing for deterministic testing
type Config ¶
type Config struct {
// Issuer is the server's issuer identifier (base URL)
Issuer string
// AuthorizationCodeTTL is how long authorization codes are valid
AuthorizationCodeTTL int64 // seconds, default: 600 (10 minutes)
// AccessTokenTTL is how long access tokens are valid
AccessTokenTTL int64 // seconds, default: 3600 (1 hour)
// RefreshTokenTTL is how long refresh tokens are valid
RefreshTokenTTL int64 // seconds, default: 7776000 (90 days)
// ProviderTokenTTL is how long provider tokens (from upstream IdP like Dex/Google) are stored
// for SSO token forwarding. This is INDEPENDENT of the access token's actual expiry.
//
// Provider tokens are saved by userID and email in HandleProviderCallback to enable:
// - SSO token forwarding (extracting id_token for downstream services)
// - Token refresh (using refresh_token to get new tokens)
//
// If the provider's access token has a short expiry (e.g., 5 minutes), this TTL ensures
// the token remains available in storage for the user's session duration.
// When a refresh_token is present, the server can refresh expired access tokens.
//
// Default: 86400 seconds (24 hours)
// Recommended: Set to your expected session duration or longer
ProviderTokenTTL int64 // seconds, default: 86400 (24 hours)
// AllowRefreshTokenRotation enables refresh token rotation (OAuth 2.1)
// Default: true (secure by default)
AllowRefreshTokenRotation bool // default: true
// TrustProxy enables trusting X-Forwarded-For and X-Real-IP headers
// WARNING: Only enable if behind a trusted reverse proxy (nginx, HAProxy, etc.)
// When false, uses direct connection IP (secure by default)
// Default: false
TrustProxy bool // default: false
// TrustedProxyCount is the number of trusted proxies in front of this server
// Used with TrustProxy to correctly extract client IP from X-Forwarded-For
// Example: If you have 2 proxies (CloudFlare + nginx), set this to 2
// The client IP will be extracted as: ips[len(ips) - TrustedProxyCount - 1]
// Default: 1
TrustedProxyCount int // default: 1
// MaxClientsPerIP limits client registrations per IP address
// Prevents DoS via mass client registration
// Default: 10
MaxClientsPerIP int // default: 10
// MaxRegistrationsPerHour limits client registrations per IP address per hour
// This is a time-windowed rate limit that prevents resource exhaustion
// through repeated registration/deletion cycles
// Default: 10
MaxRegistrationsPerHour int // default: 10
// RegistrationRateLimitWindow is the time window for client registration rate limiting
// Default: 1 hour
RegistrationRateLimitWindow int64 // seconds, default: 3600 (1 hour)
// ClockSkewGracePeriod is the grace period for token expiration checks (in seconds)
// This prevents false expiration errors due to time synchronization issues
// Default: 5 seconds
ClockSkewGracePeriod int64 // seconds, default: 5
// TokenRefreshThreshold is the time before token expiry (in seconds) when proactive
// refresh should be attempted during token validation. If a token will expire within
// this threshold and has a refresh token available, ValidateToken will attempt to
// refresh it proactively to avoid validation failures.
// This improves user experience by preventing expired token errors when refresh is possible.
// Default: 300 seconds (5 minutes)
TokenRefreshThreshold int64 // seconds, default: 300
// ProviderRevocationTimeout is the timeout PER TOKEN for revoking tokens at the provider (Google/GitHub/etc)
// during security events (code reuse, token reuse detection).
// This prevents blocking indefinitely if the provider is slow or unreachable.
// Default: 10 seconds per token (allows for network latency and rate limits)
ProviderRevocationTimeout int64 // seconds, default: 10
// ProviderRevocationMaxRetries is the maximum number of retry attempts for provider revocation
// Retries use exponential backoff: 100ms, 200ms, 400ms, 800ms, 1600ms
// Default: 3 retries (total max time per token: ~10s + ~3s retries = ~13s)
ProviderRevocationMaxRetries int // default: 3
// ProviderRevocationFailureThreshold is the maximum acceptable failure rate (0.0 to 1.0)
// If more than this percentage of provider revocations fail, the entire operation fails
// to ensure tokens aren't left valid at the provider during security events.
// Default: 0.5 (50% - at least half must succeed)
ProviderRevocationFailureThreshold float64 // default: 0.5
// RevokedFamilyRetentionDays is the number of days to retain revoked token family metadata
// for forensics and security auditing. After this period, revoked family metadata is deleted.
// Longer retention enables better security incident investigation but uses more memory.
// Default: 90 days (recommended for security compliance and forensics)
RevokedFamilyRetentionDays int64 // days, default: 90
// SupportedScopes lists the scopes that are allowed for clients
// If empty, all scopes are allowed
SupportedScopes []string
// ResourceMetadataByPath enables per-path Protected Resource Metadata (RFC 9728).
// This allows different protected resources to advertise different authorization
// requirements. When a client requests /.well-known/oauth-protected-resource/<path>,
// the server returns metadata specific to that path.
//
// Keys are path prefixes (e.g., "/mcp/files", "/mcp/admin").
// If a request path matches multiple prefixes, the longest match is used.
// If no match is found, the default server-wide metadata is returned.
//
// Example:
// ResourceMetadataByPath: map[string]ProtectedResourceConfig{
// "/mcp/files": {ScopesSupported: []string{"files:read", "files:write"}},
// "/mcp/admin": {ScopesSupported: []string{"admin:access"}},
// }
//
// Discovery endpoints registered:
// - /.well-known/oauth-protected-resource (default metadata)
// - /.well-known/oauth-protected-resource/mcp/files (files-specific metadata)
// - /.well-known/oauth-protected-resource/mcp/admin (admin-specific metadata)
ResourceMetadataByPath map[string]ProtectedResourceConfig
// MaxScopeLength is the maximum allowed length for the scope parameter string
// This prevents potential DoS attacks via extremely long scope strings.
// The scope string is space-delimited, so this limits the total length including
// all scopes and spaces, not individual scope names.
// Default: 1000 characters (sufficient for most use cases)
// Example: "openid profile email" = 22 characters
MaxScopeLength int // default: 1000
// MaxRequestBodySize is the maximum allowed size in bytes for incoming HTTP request bodies.
// This prevents denial-of-service attacks via oversized POST bodies by wrapping the
// request body with http.MaxBytesReader before parsing form data or JSON payloads.
// Requests exceeding this limit receive a 413 Request Entity Too Large response.
// Default: 1048576 (1 MiB, generous for OAuth form data which is typically a few KB)
MaxRequestBodySize int64 // bytes, default: 1048576 (1 MiB)
// DefaultChallengeScopes are the scopes to include in WWW-Authenticate challenges
// When a 401 Unauthorized response is returned, these scopes indicate what
// permissions would be needed to access the resource.
// Per MCP 2025-11-25, this helps clients determine which scopes to request.
// If empty, no scope parameter is included in WWW-Authenticate headers.
DefaultChallengeScopes []string
// DisableWWWAuthenticateMetadata disables resource_metadata and discovery parameters
// in WWW-Authenticate headers for backward compatibility with legacy OAuth clients.
// When false (default): Full MCP 2025-11-25 compliance with enhanced discovery support
// - Includes resource_metadata URL for authorization server discovery
// - Includes scope parameter (if DefaultChallengeScopes configured)
// - Includes error and error_description parameters
// When true: Minimal WWW-Authenticate headers for backward compatibility
// - Only includes "Bearer" scheme without parameters
// - Compatible with older OAuth clients that may not expect parameters
// Default: false (metadata ENABLED for secure by default, MCP 2025-11-25 compliant)
//
// WARNING: Only enable if you have legacy OAuth clients that cannot handle
// parameters in WWW-Authenticate headers. Modern clients will ignore unknown
// parameters per HTTP specifications.
//
// Use case for enabling (disabling metadata):
// - Testing with legacy OAuth clients
// - Gradual migration period for clients updating to MCP 2025-11-25
// - Troubleshooting client compatibility issues
DisableWWWAuthenticateMetadata bool // default: false (metadata ENABLED)
// EndpointScopeRequirements maps HTTP paths to required scopes for MCP 2025-11-25 scope validation.
// When a protected endpoint is accessed, the token's scopes are validated against these requirements.
// If the token lacks required scopes, a 403 with insufficient_scope error is returned.
//
// Path Matching:
// - Exact match: "/api/files" matches only "/api/files"
// - Prefix match: "/api/files/*" matches "/api/files/..." (any sub-path)
//
// Example:
// EndpointScopeRequirements: map[string][]string{
// "/api/files/*": {"files:read", "files:write"},
// "/api/admin/*": {"admin:access"},
// "/api/user/profile": {"user:profile"},
// }
//
// Scope Validation Logic:
// - If no requirements configured for a path, access is allowed (no scope check)
// - If requirements exist, ALL required scopes must be present in the token
// - Scope validation follows OAuth 2.0 semantics (exact string matching)
//
// Default: nil (no endpoint-specific scope requirements)
EndpointScopeRequirements map[string][]string
// EndpointMethodScopeRequirements maps HTTP paths AND methods to required scopes.
// This extends EndpointScopeRequirements with method-aware scope checking.
// Useful when different HTTP methods require different scopes (e.g., GET vs POST).
//
// Path Matching (same as EndpointScopeRequirements):
// - Exact match: "/api/files" matches only "/api/files"
// - Prefix match: "/api/files/*" matches "/api/files/..." (any sub-path)
//
// Method Matching:
// - Use "*" as method to match any HTTP method (fallback)
// - Method names are case-sensitive and should be uppercase (GET, POST, etc.)
//
// Example:
// EndpointMethodScopeRequirements: map[string]map[string][]string{
// "/api/files/*": {
// "GET": {"files:read"},
// "POST": {"files:write"},
// "DELETE": {"files:delete", "admin:access"},
// "*": {"files:read"}, // fallback for other methods
// },
// }
//
// Precedence:
// 1. EndpointMethodScopeRequirements with exact method match
// 2. EndpointMethodScopeRequirements with "*" method (fallback)
// 3. EndpointScopeRequirements (method-agnostic)
// 4. No requirements (access allowed)
//
// Default: nil (no method-specific scope requirements)
EndpointMethodScopeRequirements map[string]map[string][]string
// HideEndpointPathInErrors controls whether endpoint paths are included in error messages.
// When true, error messages will not include the specific endpoint path, providing
// defense against information disclosure.
//
// When false (default): Error messages include the path for debugging
// "Token lacks required scopes for endpoint /api/admin/users"
//
// When true: Error messages use a generic message
// "Token lacks required scopes for this endpoint"
//
// Security Consideration:
// Including paths in error messages aids debugging but could reveal internal
// API structure to attackers. Enable this in production if path disclosure is a concern.
//
// Default: false (paths included in errors for easier debugging)
HideEndpointPathInErrors bool
// AllowPKCEPlain allows the 'plain' code_challenge_method (NOT RECOMMENDED)
// WARNING: The 'plain' method is insecure and deprecated in OAuth 2.1
// Only enable for backward compatibility with legacy clients
// When false, only S256 method is accepted (secure by default)
// Default: false
AllowPKCEPlain bool // default: false
// RequirePKCE enforces PKCE for all authorization requests
// WARNING: Disabling this significantly weakens security
// Only disable for backward compatibility with very old clients
// When true, code_challenge parameter is mandatory (secure by default)
// Default: true
RequirePKCE bool // default: true
// AllowPublicClientsWithoutPKCE allows public clients to authenticate without PKCE
// WARNING: This creates a significant security vulnerability to authorization code theft attacks
// Public clients (mobile apps, SPAs) cannot securely store credentials, making them vulnerable
// to authorization code interception if PKCE is not used (OAuth 2.1 Section 7.6)
// Only enable this for backward compatibility with legacy clients that cannot be updated
// SECURITY: Even when RequirePKCE=false, public clients MUST use PKCE unless this is explicitly enabled
// Default: false (PKCE is REQUIRED for public clients per OAuth 2.1)
AllowPublicClientsWithoutPKCE bool // default: false
// MinStateLength is the minimum length for state parameters to prevent
// timing attacks and ensure sufficient entropy for CSRF protection.
// OAuth 2.1 recommends at least 128 bits (16 bytes) of entropy.
// Default: 24 characters (144 bits of entropy)
MinStateLength int // default: 24
// AllowNoStateParameter allows authorization requests without the state parameter.
// WARNING: Disabling state parameter validation weakens CSRF protection!
// The state parameter is REQUIRED by OAuth 2.1 for CSRF attack prevention.
// Only enable this for compatibility with clients that don't support state (e.g., some MCP clients).
// Default: false (state is REQUIRED for security)
AllowNoStateParameter bool // default: false
// AllowPublicClientRegistration controls two security aspects of client registration:
// 1. Whether the DCR endpoint (/oauth/register) requires authentication (Bearer token)
// 2. Whether public clients (native apps, CLIs with token_endpoint_auth_method="none") can be registered
//
// When false (SECURE DEFAULT):
// - DCR endpoint REQUIRES a valid RegistrationAccessToken in Authorization header
// - Public client registration is DENIED (only confidential clients can be registered)
// - This prevents both DoS attacks and unauthorized public client creation
//
// When true (PERMISSIVE, for development only):
// - DCR endpoint allows UNAUTHENTICATED registration (DoS risk)
// - Public clients CAN be registered by any requester
// - Should only be used in trusted development environments
//
// SECURITY RECOMMENDATION: Keep this false in production. Use RegistrationAccessToken
// to authenticate trusted client developers, and only enable public clients if your
// use case requires native/mobile apps.
//
// Default: false (authentication REQUIRED, public clients DENIED)
AllowPublicClientRegistration bool // default: false
// RegistrationAccessToken is the Bearer token required for client registration
// when AllowPublicClientRegistration is false (recommended for production).
//
// Generate a cryptographically secure random token and share it ONLY with
// trusted developers who need to register OAuth clients.
//
// Example generation: openssl rand -base64 32
//
// The token is validated using constant-time comparison to prevent timing attacks.
// If AllowPublicClientRegistration is false but this is empty, ALL registration
// attempts will fail (misconfiguration) unless TrustedPublicRegistrationSchemes
// is configured and the client uses trusted redirect URI schemes.
//
// Default: "" (no token configured)
RegistrationAccessToken string
// TrustedPublicRegistrationSchemes lists URI schemes that are allowed for
// unauthenticated client registration. Clients registering with redirect URIs
// using ONLY these schemes do NOT need a RegistrationAccessToken.
//
// This enables compatibility with MCP clients like Cursor that don't support
// registration tokens, while maintaining security for other clients.
//
// Security: Custom URI schemes (cursor://, vscode://) can only be intercepted
// by the application that registered the scheme with the OS. This makes them
// inherently safe for public registration - an attacker cannot register a
// malicious client with cursor:// because they can't receive the callback.
//
// Scheme matching is case-insensitive (per RFC 3986 Section 3.1).
// Schemes are normalized to lowercase during configuration validation.
//
// Example: ["cursor", "vscode", "vscode-insiders", "windsurf"]
// Default: [] (all registrations require token unless AllowPublicClientRegistration=true)
TrustedPublicRegistrationSchemes []string
// DisableStrictSchemeMatching explicitly disables strict scheme matching for deployments
// that need to support clients with mixed redirect URI schemes (e.g., cursor:// AND https://).
//
// Strict scheme matching (enabled by default when TrustedPublicRegistrationSchemes is configured):
// - All redirect URIs MUST use schemes from TrustedPublicRegistrationSchemes
// - A mix of trusted and untrusted schemes requires a registration token
// - Provides maximum security by preventing token leakage to untrusted URIs
//
// When disabled (permissive mode):
// - If ANY redirect URI uses a trusted scheme, registration is allowed
// - Other redirect URIs can use any scheme (including https://)
// - Use case: Clients that need both custom scheme and web-based callbacks
// - A security warning is logged when this mode is used
//
// WARNING: Disabling strict matching allows clients to register with untrusted redirect URIs
// alongside trusted ones. While PKCE mitigates code interception, this reduces security.
// Only set this to true if you have specific requirements for mixed scheme clients.
// Default: false (strict matching is enabled when TrustedPublicRegistrationSchemes is configured)
DisableStrictSchemeMatching bool
// AllowedCustomSchemes is a list of allowed custom URI scheme patterns (regex)
// Used for validating custom redirect URIs (e.g., myapp://, com.example.app://)
// Empty list allows all RFC 3986 compliant schemes
// Default: ["^[a-z][a-z0-9+.-]*$"] (RFC 3986 compliant schemes)
AllowedCustomSchemes []string
// ProductionMode enforces strict security validation for redirect URIs:
// - HTTPS required for all redirect URIs (except loopback when AllowLocalhostRedirectURIs=true)
// - Private IP addresses blocked in redirect URIs (unless AllowPrivateIPRedirectURIs=true)
// - Link-local addresses blocked (unless AllowLinkLocalRedirectURIs=true)
// - Dangerous URI schemes blocked (javascript:, data:, file:, etc.)
// Default: true (secure by default - set automatically by applySecurityDefaults)
// To disable for development, set DisableProductionMode=true instead.
ProductionMode bool
// DisableProductionMode explicitly disables ProductionMode for development environments.
// WARNING: Disabling ProductionMode significantly weakens redirect URI security.
// Only set this to true for local development where you need HTTP on non-loopback hosts.
// Default: false (ProductionMode is enabled)
DisableProductionMode bool
// AllowLocalhostRedirectURIs allows http://localhost and http://127.0.0.1 redirect URIs
// even in ProductionMode. Required for native apps per RFC 8252.
// Also allows loopback IPv6 addresses (::1, [::1]).
// Default: false (Go zero-value; set to true for native app support per RFC 8252 Section 7.3)
AllowLocalhostRedirectURIs bool
// AllowPrivateIPRedirectURIs allows redirect URIs that resolve to private IP addresses
// (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 per RFC 1918).
// WARNING: Enables SSRF attacks to internal networks if not properly secured.
// Only enable for internal/VPN deployments where clients legitimately use private IPs.
// Default: false (blocked for security)
AllowPrivateIPRedirectURIs bool
// AllowLinkLocalRedirectURIs allows link-local addresses (169.254.0.0/16, fe80::/10).
// WARNING: Could enable access to cloud metadata services (SSRF to 169.254.169.254).
// This is a significant security risk in cloud environments (AWS, GCP, Azure).
// Only enable if you have specific requirements for link-local addresses.
// Default: false (blocked for security)
AllowLinkLocalRedirectURIs bool
// AllowPrivateIPClientMetadata allows CIMD (Client ID Metadata Document) metadata URLs
// that resolve to private IP addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 per RFC 1918).
// This also allows loopback addresses (127.0.0.0/8, ::1) and link-local addresses.
// WARNING: Reduces SSRF protection. Only enable for internal/VPN deployments where
// MCP servers legitimately communicate over private networks.
//
// Use cases:
// - Home lab deployments
// - Air-gapped environments
// - Internal enterprise networks
// - Any deployment where MCP servers communicate over private networks
//
// Security: When enabled, the server will fetch client metadata from URLs that resolve
// to private IPs. This is necessary when MCP aggregators and servers are on the same
// internal network. PKCE and other OAuth security measures still apply.
//
// Default: false (blocked for security)
AllowPrivateIPClientMetadata bool
// AllowPrivateIPJWKS allows JWKS endpoints to resolve to private IP addresses
// (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 per RFC 1918) during SSO token
// validation (TrustedAudiences). This also allows loopback addresses (127.0.0.0/8, ::1)
// and link-local addresses.
//
// This is necessary for private IdP deployments where Dex or another OIDC provider
// runs on internal networks, and SSO token forwarding is used.
//
// WARNING: Reduces SSRF protection for JWKS fetching only. Only enable for
// internal/VPN deployments where the IdP legitimately runs on private networks.
//
// Use cases:
// - Home lab deployments with internal Dex
// - Air-gapped environments
// - Enterprise deployments with private IdPs
//
// Note: For Google OAuth, this setting has no effect as Google's JWKS endpoint
// is always publicly accessible.
//
// Default: false (blocked for security)
AllowPrivateIPJWKS bool
// BlockedRedirectSchemes lists URI schemes that are always rejected for security.
// These schemes can be used for XSS attacks (javascript:, data:, blob:) or local file/app access (file:, ms-appx:).
// This is applied in ALL modes (production and development).
// Default: ["javascript", "data", "file", "vbscript", "about", "ftp", "blob", "ms-appx", "ms-appx-web"]
// Override to customize blocked schemes (empty list uses defaults).
BlockedRedirectSchemes []string
// DNSValidation enables DNS resolution of redirect URI hostnames to validate
// they don't resolve to private/internal IPs (defense against DNS rebinding attacks).
// When enabled, hostnames are resolved and the resulting IP is checked.
// Default: true (secure by default - set automatically by applySecurityDefaults)
// To disable, set DisableDNSValidation=true instead.
DNSValidation bool
// DisableDNSValidation explicitly disables DNS validation for redirect URI hostnames.
// WARNING: Disabling DNS validation allows potential DNS rebinding attacks.
// Only set this to true if DNS lookup latency during client registration is unacceptable.
// Default: false (DNSValidation is enabled)
DisableDNSValidation bool
// DNSValidationStrict enables fail-closed behavior for DNS validation.
// When true AND DNSValidation=true:
// - DNS resolution failures BLOCK client registration (fail-closed)
// - This prevents attackers from bypassing validation by causing DNS failures
// When false:
// - DNS resolution failures are logged but registration is allowed (fail-open)
// - This may allow bypass of DNS validation via intentional DNS failures
// Default: true (secure by default - set automatically by applySecurityDefaults)
// To disable strict mode, set DisableDNSValidationStrict=true instead.
DNSValidationStrict bool
// DisableDNSValidationStrict explicitly disables fail-closed DNS validation.
// WARNING: Disabling strict mode allows potential DNS validation bypass via intentional failures.
// Only set this to true if DNS reliability issues cause unacceptable registration failures.
// Default: false (DNSValidationStrict is enabled)
DisableDNSValidationStrict bool
// DNSValidationTimeout is the timeout for DNS resolution when DNSValidation=true.
// Prevents slow DNS from blocking registration.
// Default: 2 seconds
DNSValidationTimeout time.Duration
// DNSResolver is the resolver used for DNS lookups during redirect URI validation.
// This is primarily for testing - allows injecting a mock resolver.
// If nil, the default net.DefaultResolver is used.
// Default: nil (uses net.DefaultResolver)
DNSResolver DNSResolver
// ValidateRedirectURIAtAuthorization enables re-validation of redirect URIs during
// authorization requests, not just at client registration time.
// This provides defense against TOCTOU (Time-of-Check to Time-of-Use) attacks where:
// 1. Attacker registers with a hostname resolving to a public IP
// 2. Later changes DNS to resolve to an internal IP (DNS rebinding)
// When enabled, the same security checks applied at registration are repeated
// at authorization time, catching DNS rebinding attacks.
// Default: true (secure by default - set automatically by applySecurityDefaults)
// To disable, set DisableAuthorizationTimeValidation=true instead.
ValidateRedirectURIAtAuthorization bool
// DisableAuthorizationTimeValidation explicitly disables redirect URI validation at authorization time.
// WARNING: Disabling this allows DNS rebinding attacks between registration and use.
// Only set this to true if authorization latency is critical and you accept the TOCTOU risk.
// Default: false (ValidateRedirectURIAtAuthorization is enabled)
DisableAuthorizationTimeValidation bool
// AllowInsecureHTTP allows running OAuth server over HTTP (INSECURE - development only)
// WARNING: OAuth over HTTP exposes all tokens and credentials to network interception
// This should ONLY be enabled for local development (localhost, 127.0.0.1)
// When false (default), the server enforces HTTPS for non-localhost deployments
// Security: must be explicitly enabled to allow HTTP
AllowInsecureHTTP bool
// Storage and cleanup configuration
StorageCleanupInterval time.Duration // How often to clean up expired tokens/codes (default: 1 minute)
RateLimiterCleanupInterval time.Duration // How often to clean up idle rate limiters (default: 5 minutes)
// CORS settings for browser-based clients
CORS CORSConfig
// Instrumentation settings for observability
Instrumentation InstrumentationConfig
// Interstitial configures the OAuth success interstitial page for custom URL schemes.
// Per RFC 8252 Section 7.1, browsers may fail silently on 302 redirects to custom
// URL schemes (cursor://, vscode://, etc.). The interstitial page provides visual
// feedback and a manual fallback button.
// Optional: if nil, the default interstitial page is used.
Interstitial *InterstitialConfig
// ResourceIdentifier is the canonical URI that identifies this MCP resource server (RFC 8707)
// Used for audience validation to ensure tokens are only accepted by their intended resource server
// If empty, defaults to Issuer value
// Example: "https://mcp.example.com" or "https://api.example.com/mcp"
// Security: This prevents token theft and replay attacks to different resource servers
ResourceIdentifier string
// TrustedAudiences lists additional OAuth client IDs whose tokens are accepted.
// This enables Single Sign-On (SSO) scenarios where tokens issued to a trusted upstream
// (e.g., an MCP aggregator like muster) are accepted by this resource server.
//
// Security Model:
// - The server's own ClientID (via ResourceIdentifier) is always implicitly trusted
// - Each trusted audience must be explicitly configured (no implicit trust)
// - Tokens are only accepted if they're from the configured issuer (same IdP)
// - An audit event (EventCrossClientTokenAccepted) is logged when a token is accepted
// via cross-client trust for security monitoring
//
// Use Case:
// In architectures with an MCP aggregator (like muster) that proxies requests to
// downstream MCP servers, users authenticate to the aggregator and receive tokens
// with the aggregator's client_id as the audience. By adding the aggregator's
// client_id to TrustedAudiences, downstream servers can accept these forwarded
// tokens without requiring separate authentication flows.
//
// Example:
// TrustedAudiences: []string{"muster-client", "my-aggregator-client"}
// This accepts tokens issued to either "muster-client" or "my-aggregator-client"
// in addition to tokens issued to this server's own ResourceIdentifier.
//
// Default: nil (only tokens for this server's ResourceIdentifier are accepted)
TrustedAudiences []string
// EnableClientIDMetadataDocuments enables URL-based client_id support per MCP 2025-11-25
// When enabled, clients can use HTTPS URLs as client identifiers, and the authorization
// server will fetch client metadata from that URL following draft-ietf-oauth-client-id-metadata-document-00
// This addresses the common MCP scenario where servers and clients have no pre-existing relationship.
// Default: false (disabled for backward compatibility)
EnableClientIDMetadataDocuments bool
// ClientMetadataFetchTimeout is the timeout for fetching client metadata from URL-based client_ids
// This prevents indefinite blocking if a metadata URL is slow or unresponsive
// Default: 10 seconds
ClientMetadataFetchTimeout time.Duration
// EnableRevocationEndpoint controls whether the OAuth 2.0 Token Revocation endpoint (RFC 7009)
// is advertised in Authorization Server Metadata and available for use.
// When true: revocation_endpoint will be included in /.well-known/oauth-authorization-server
// When false: revocation_endpoint will NOT be advertised (endpoint not yet implemented)
// SECURITY: Only enable when you have implemented the actual revocation endpoint handler
// Default: false (not yet implemented)
EnableRevocationEndpoint bool
// EnableIntrospectionEndpoint controls whether the OAuth 2.0 Token Introspection endpoint (RFC 7662)
// is advertised in Authorization Server Metadata and available for use.
// When true: introspection_endpoint will be included in /.well-known/oauth-authorization-server
// When false: introspection_endpoint will NOT be advertised (endpoint not yet implemented)
// SECURITY: Only enable when you have implemented the actual introspection endpoint handler
// Default: false (not yet implemented)
EnableIntrospectionEndpoint bool
// ClientMetadataCacheTTL is how long to cache fetched client metadata
// Caching reduces latency and prevents repeated fetches for the same client
// HTTP Cache-Control headers may override this value
// Default: 5 minutes
ClientMetadataCacheTTL time.Duration
// contains filtered or unexported fields
}
Config holds OAuth server configuration
func HighSecurityRedirectURIConfig ¶ added in v0.2.18
func HighSecurityRedirectURIConfig() *Config
HighSecurityRedirectURIConfig returns a Config with strict redirect URI security settings. This is a convenience function for high-security deployments.
Settings enabled: - ProductionMode=true: HTTPS required for non-loopback - AllowLocalhostRedirectURIs=true: RFC 8252 native app support - AllowPrivateIPRedirectURIs=false: Block SSRF to internal networks - AllowLinkLocalRedirectURIs=false: Block cloud metadata SSRF - DNSValidation=true: Resolve hostnames to check IPs - DNSValidationStrict=true: Fail-closed on DNS failures - ValidateRedirectURIAtAuthorization=true: Catch DNS rebinding
Use this as a starting point and adjust for your environment:
config := server.HighSecurityRedirectURIConfig() config.Issuer = "https://auth.example.com" config.AllowPrivateIPRedirectURIs = true // For internal deployments
func (*Config) AuthorizationEndpoint ¶ added in v0.1.27
AuthorizationEndpoint returns the full URL to the authorization endpoint
func (*Config) GetResourceIdentifier ¶ added in v0.1.34
GetResourceIdentifier returns the resource identifier for this server If ResourceIdentifier is explicitly configured, returns that value Otherwise, defaults to the Issuer value (secure default) Per RFC 8707, this identifier is used for token audience binding
func (*Config) IntrospectionEndpoint ¶ added in v0.1.43
IntrospectionEndpoint returns the full URL to the RFC 7662 token introspection endpoint
func (*Config) ProtectedResourceMetadataEndpoint ¶ added in v0.1.31
ProtectedResourceMetadataEndpoint returns the full URL to the RFC 9728 Protected Resource Metadata endpoint This endpoint is used in WWW-Authenticate headers to help MCP clients discover authorization server information
func (*Config) RegistrationEndpoint ¶ added in v0.1.27
RegistrationEndpoint returns the full URL to the dynamic client registration endpoint
func (*Config) RevocationEndpoint ¶ added in v0.1.43
RevocationEndpoint returns the full URL to the RFC 7009 token revocation endpoint
func (*Config) SetTrustedSchemesMap ¶ added in v0.2.19
SetTrustedSchemesMap builds the pre-computed trusted schemes map from the given schemes. This is primarily used for testing purposes. In production, the map is built automatically by validateTrustedPublicRegistrationSchemes during config validation. Schemes are normalized to lowercase for case-insensitive matching.
func (*Config) TokenEndpoint ¶ added in v0.1.27
TokenEndpoint returns the full URL to the token endpoint
type DNSResolver ¶ added in v0.2.18
type DNSResolver interface {
// LookupIP looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
LookupIP(ctx context.Context, network, host string) ([]net.IP, error)
}
DNSResolver is an interface for DNS resolution, allowing for dependency injection in testing. The default implementation uses net.DefaultResolver.
This interface is intentionally minimal - it only exposes the method needed for redirect URI validation.
type InstrumentationConfig ¶ added in v0.1.26
type InstrumentationConfig struct {
// Enabled controls whether instrumentation is active
// When false, uses no-op providers (zero overhead)
// Default: false (disabled)
Enabled bool
// ServiceName is the name of the service for telemetry
// Default: "mcp-oauth"
ServiceName string
// ServiceVersion is the version of the service for telemetry
// Default: "unknown"
ServiceVersion string
// LogClientIPs controls whether client IP addresses are included in traces and metrics
// When false, client IP attributes will be omitted from observability data
// This can help with GDPR and privacy compliance in strict jurisdictions
// Default: false (disabled for privacy by default)
//
// Privacy Note: Client IP addresses may be considered Personally Identifiable
// Information (PII) under GDPR and other privacy regulations. Enable IP
// logging only if required for security monitoring and you have appropriate
// legal basis and data protection measures in place.
LogClientIPs bool
// IncludeClientIDInMetrics controls whether client_id is included in metric labels
// When true, provides detailed per-client metrics but increases cardinality
// When false, reduces cardinality (recommended for >1000 clients)
// Default: true (include client_id for detailed metrics)
//
// Cardinality Warning: Each unique client_id creates a new time series.
// With 10,000+ clients, this can cause memory and performance issues.
// Set to false for high-scale deployments.
IncludeClientIDInMetrics bool
// MetricsExporter controls which metrics exporter to use
// Options: "prometheus", "stdout", "none" (default: "none")
// - "prometheus": Export metrics in Prometheus format (use inst.PrometheusExporter())
// - "stdout": Print metrics to stdout (useful for development/debugging)
// - "none": Use no-op provider (zero overhead)
// Default: "none" (disabled)
MetricsExporter string
// TracesExporter controls which traces exporter to use
// Options: "otlp", "stdout", "none" (default: "none")
// - "otlp": Export traces via OTLP HTTP (requires OTLPEndpoint)
// - "stdout": Print traces to stdout (useful for development/debugging)
// - "none": Use no-op provider (zero overhead)
// Default: "none" (disabled)
TracesExporter string
// OTLPEndpoint is the endpoint for OTLP trace export
// Required when TracesExporter="otlp"
// Example: "localhost:4318" (default OTLP HTTP port)
// Default: "" (not set)
OTLPEndpoint string
// OTLPInsecure controls whether to use insecure HTTP for OTLP export
// When false (default), uses TLS for secure transport
// Set to true only for local development or testing
// Default: false (uses TLS)
// WARNING: Never use in production - traces contain user metadata
OTLPInsecure bool
}
InstrumentationConfig holds configuration for OpenTelemetry instrumentation
type InterstitialBranding ¶ added in v0.1.48
type InterstitialBranding struct {
// LogoURL is an optional URL to a logo image (PNG, SVG, JPEG).
// Must be HTTPS for security (validated at startup). HTTP is only allowed
// when AllowInsecureHTTP is enabled for local development.
// Leave empty to use the default animated checkmark icon.
// Recommended size: 80x80 pixels or larger (displayed at 80px height).
//
// Security: Host on a trusted CDN with immutable URLs. The image is loaded
// with crossorigin="anonymous" for better security isolation.
LogoURL string
// LogoAlt is the alt text for the logo image.
// Required for accessibility if LogoURL is set.
// Default: "Logo" (if LogoURL is set)
LogoAlt string
// Title replaces the "Authorization Successful" heading.
// Example: "Connected to Acme Corp"
Title string
// Message replaces the default success message.
// Use {{.AppName}} placeholder for the application name.
// Example: "You have been authenticated with {{.AppName}}. You can now close this window."
// Default: "You have been authenticated successfully. Return to {{.AppName}} to continue."
Message string
// ButtonText replaces the "Open [AppName]" button text.
// Use {{.AppName}} placeholder for the application name.
// Example: "Return to {{.AppName}}"
// Default: "Open {{.AppName}}"
ButtonText string
// PrimaryColor is the primary/accent color for buttons and highlights.
// Must be a valid CSS color value (hex, rgb, hsl, or named color).
// Examples: "#4F46E5", "rgb(79, 70, 229)", "indigo"
// Default: "#00d26a" (green)
PrimaryColor string
// BackgroundGradient is the body background CSS value.
// Can be a solid color, gradient, or any valid CSS background value.
// Example: "linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%)"
// Default: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)"
BackgroundGradient string
// CustomCSS is additional CSS to inject into the page.
// This CSS is added after the default styles, allowing overrides.
// SECURITY: Must not contain "</style>" to prevent injection attacks.
// Validated at startup - server will panic if invalid.
// Example: ".container { max-width: 600px; }"
CustomCSS string
}
InterstitialBranding configures visual elements of the default interstitial page. All fields are optional - unset fields use the default values.
Security Best Practices:
- LogoURL must use HTTPS (enforced at startup)
- Host logos on trusted CDNs or your own infrastructure
- Use immutable/versioned URLs for logos (e.g., include hash in filename)
- Note: Browsers don't support Subresource Integrity (SRI) for images, so HTTPS and trusted hosting are your primary security controls
type InterstitialConfig ¶ added in v0.1.48
type InterstitialConfig struct {
// CustomHandler allows complete control over the interstitial response.
// When set, this handler is called instead of rendering any template.
// The handler receives the redirect URL and app name as request context values.
// Use oauth.InterstitialRedirectURL(ctx) and oauth.InterstitialAppName(ctx) to extract them.
//
// SECURITY: The handler MUST set appropriate security headers.
// Use security.SetInterstitialSecurityHeaders() as a baseline.
// If you include custom inline scripts, you must update CSP headers accordingly.
//
// Example:
// CustomHandler: func(w http.ResponseWriter, r *http.Request) {
// redirectURL := oauth.InterstitialRedirectURL(r.Context())
// appName := oauth.InterstitialAppName(r.Context())
// security.SetInterstitialSecurityHeaders(w, "https://auth.example.com")
// // Serve your custom response...
// }
CustomHandler func(w http.ResponseWriter, r *http.Request)
// CustomTemplate is a custom HTML template string using Go's html/template syntax.
// Available template variables:
// - {{.RedirectURL}} - The OAuth callback redirect URL (marked safe for href)
// - {{.AppName}} - Human-readable application name (e.g., "Cursor", "Visual Studio Code")
// - All InterstitialBranding fields ({{.LogoURL}}, {{.Title}}, etc.)
//
// SECURITY: The template is parsed using html/template which auto-escapes HTML.
// If you include inline scripts, you must update CSP headers accordingly.
// Consider using security.SetInterstitialSecurityHeaders() with custom script hashes.
//
// Ignored if CustomHandler is set.
CustomTemplate string
// Branding allows customization of the default template's appearance.
// This is the simplest way to add your organization's branding without
// providing a complete custom template.
//
// Ignored if CustomHandler or CustomTemplate is set.
Branding *InterstitialBranding
}
InterstitialConfig configures the OAuth success interstitial page displayed when redirecting to custom URL schemes (cursor://, vscode://, etc.)
Per RFC 8252 Section 7.1, browsers may fail silently on 302 redirects to custom URL schemes. The interstitial page provides visual feedback and a manual fallback.
Configuration Priority:
- CustomHandler - if set, takes full control of the response
- CustomTemplate - if set, uses the provided HTML template
- Branding - if set, customizes the default template's appearance
- Default - uses the built-in template with standard styling
type ProtectedResourceConfig ¶ added in v0.1.50
type ProtectedResourceConfig struct {
// ScopesSupported lists the scopes required/supported for this specific resource path.
// If empty, falls back to the server's default SupportedScopes configuration.
// Per RFC 9728, this helps clients determine what access they need to request.
ScopesSupported []string
// AuthorizationServers lists the authorization server URLs for this resource.
// If empty, defaults to the server's Issuer.
// This allows different resource paths to point to different authorization servers.
AuthorizationServers []string
// BearerMethodsSupported specifies how bearer tokens can be sent.
// Default: ["header"] (Authorization header only).
// Options: "header", "body", "query" (per RFC 6750).
// Note: "body" and "query" are discouraged for security reasons.
BearerMethodsSupported []string
// ResourceIdentifier is the canonical identifier for this specific resource.
// If empty, derived from the server's ResourceIdentifier or Issuer + path.
// Used for audience validation per RFC 8707.
ResourceIdentifier string
}
ProtectedResourceConfig holds per-path configuration for Protected Resource Metadata (RFC 9728). This allows different protected resources on the same domain to advertise different authorization requirements (scopes, authorization servers, etc.).
Per MCP 2025-11-25, sub-path discovery enables clients to understand the specific requirements for different endpoints on the same resource server.
Example:
ResourceMetadataByPath: map[string]ProtectedResourceConfig{
"/mcp/files": {
ScopesSupported: []string{"files:read", "files:write"},
},
"/mcp/admin": {
ScopesSupported: []string{"admin:access"},
},
}
type RedirectURISecurityError ¶ added in v0.2.18
type RedirectURISecurityError struct {
// Category is the error category for logging/metrics
Category string
// URI is the offending redirect URI (sanitized for logging)
URI string
// Reason is the detailed internal reason (for logs, not returned to client)
Reason string
// ClientMessage is the message safe to return to clients
ClientMessage string
}
RedirectURISecurityError represents a redirect URI validation error with detailed information for operators while keeping error messages generic for clients.
func (*RedirectURISecurityError) Error ¶ added in v0.2.18
func (e *RedirectURISecurityError) Error() string
type Server ¶
type Server struct {
Encryptor *security.Encryptor
Auditor *security.Auditor
RateLimiter *security.RateLimiter // IP-based rate limiter
UserRateLimiter *security.RateLimiter // User-based rate limiter (authenticated requests)
SecurityEventRateLimiter *security.RateLimiter // Rate limiter for security event logging (DoS prevention)
ClientRegistrationRateLimiter *security.ClientRegistrationRateLimiter // Time-windowed rate limiter for client registrations
Instrumentation *instrumentation.Instrumentation // OpenTelemetry instrumentation
Logger *slog.Logger
Config *Config
// contains filtered or unexported fields
}
Server implements the OAuth 2.1 server logic (provider-agnostic). It coordinates the OAuth flow using a Provider and storage backends.
func New ¶
func New( provider providers.Provider, tokenStore storage.TokenStore, clientStore storage.ClientStore, flowStore storage.FlowStore, config *Config, logger *slog.Logger, ) (*Server, error)
New creates a new OAuth server
func (*Server) CanRegisterWithTrustedScheme ¶ added in v0.2.19
func (s *Server) CanRegisterWithTrustedScheme(redirectURIs []string) (allowed bool, scheme string, err error)
CanRegisterWithTrustedScheme checks if a registration request can proceed without a registration access token based on the redirect URIs using trusted custom URI schemes.
This enables compatibility with MCP clients like Cursor that don't support registration tokens, while maintaining security for other clients.
Security: Custom URI schemes are harder to hijack than web URLs, but protection varies by platform (strong on Android App Links, moderate on macOS/Windows/iOS, weak on Linux). PKCE is the primary security control and is always enforced. See docs/security.md for platform-specific considerations.
Parameters:
- redirectURIs: The redirect URIs from the registration request
Returns:
- allowed: true if registration can proceed without a token
- scheme: the first trusted scheme found (for audit logging), empty if not allowed
- error: validation error if any URI is invalid
func (*Server) ExchangeAuthorizationCode ¶
func (s *Server) ExchangeAuthorizationCode(ctx context.Context, code, clientID, redirectURI, resource, codeVerifier string) (*oauth2.Token, string, error)
ExchangeAuthorizationCode exchanges an authorization code for tokens Returns oauth2.Token directly resource parameter is optional per RFC 8707 for backward compatibility
func (*Server) GetClient ¶
GetClient retrieves a client by ID (for use by handler) Supports both pre-registered clients and URL-based Client ID Metadata Documents (MCP 2025-11-25)
func (*Server) HandleProviderCallback ¶
func (s *Server) HandleProviderCallback(ctx context.Context, providerState, code string) (*storage.AuthorizationCode, string, error)
HandleProviderCallback handles the callback from the OAuth provider Returns: (authorizationCode, clientState, error) clientState is the original state parameter from the client for CSRF validation
func (*Server) RefreshAccessToken ¶
func (s *Server) RefreshAccessToken(ctx context.Context, refreshToken, clientID string) (*oauth2.Token, error)
RefreshAccessToken refreshes an access token using a refresh token with OAuth 2.1 rotation Returns oauth2.Token directly Implements OAuth 2.1 refresh token reuse detection for enhanced security Implements OAuth 2.1 Section 6 client binding validation
func (*Server) RegisterClient ¶
func (s *Server) RegisterClient(ctx context.Context, clientName, clientType, tokenEndpointAuthMethod string, redirectURIs []string, scopes []string, clientIP string, maxClientsPerIP int) (*storage.Client, string, error)
RegisterClient registers a new OAuth client with IP-based DoS protection tokenEndpointAuthMethod determines how the client authenticates at the token endpoint: - "none": Public client (no secret, PKCE-only auth) - used by native/CLI apps - "client_secret_basic": Confidential client (Basic Auth with secret) - default - "client_secret_post": Confidential client (POST form with secret)
Security: This function validates redirect URIs against the security configuration (ProductionMode, AllowPrivateIPRedirectURIs, etc.) to prevent SSRF and open redirect attacks.
func (*Server) RevokeAllTokensForUserClient ¶ added in v0.1.9
RevokeAllTokensForUserClient revokes all tokens for a user-client pair per OAuth 2.1. Returns error if provider revocation failure rate exceeds threshold or if local revocation fails. Logs detailed information about partial failures for operator investigation.
func (*Server) RevokeToken ¶
RevokeToken revokes a token (access or refresh)
func (*Server) SetAuditor ¶
SetAuditor sets the security auditor
func (*Server) SetClientRegistrationRateLimiter ¶ added in v0.1.20
func (s *Server) SetClientRegistrationRateLimiter(rl *security.ClientRegistrationRateLimiter)
SetClientRegistrationRateLimiter sets the time-windowed rate limiter for client registrations This prevents resource exhaustion through repeated registration/deletion cycles
func (*Server) SetEncryptor ¶
SetEncryptor sets the token encryptor for server and storage
func (*Server) SetInstrumentation ¶ added in v0.1.26
func (s *Server) SetInstrumentation(inst *instrumentation.Instrumentation)
SetInstrumentation sets the OpenTelemetry instrumentation for server and storage
func (*Server) SetMetadataFetchRateLimiter ¶ added in v0.1.36
func (s *Server) SetMetadataFetchRateLimiter(rl *security.RateLimiter)
SetMetadataFetchRateLimiter sets the per-domain rate limiter for Client ID Metadata Document fetches This prevents abuse and DoS attacks via repeated metadata fetches from different URLs Recommended: 10 requests per minute per domain
func (*Server) SetRateLimiter ¶
func (s *Server) SetRateLimiter(rl *security.RateLimiter)
SetRateLimiter sets the IP-based rate limiter
func (*Server) SetSecurityEventRateLimiter ¶ added in v0.1.11
func (s *Server) SetSecurityEventRateLimiter(rl *security.RateLimiter)
SetSecurityEventRateLimiter sets the rate limiter for security event logging This prevents DoS attacks via log flooding from repeated security events
func (*Server) SetSessionCreationHandler ¶ added in v0.2.82
func (s *Server) SetSessionCreationHandler(handler SessionCreationHandler)
SetSessionCreationHandler sets a callback that fires synchronously when a new token family is created during authorization code exchange. This lets consumers initialize per-session state (e.g., establish SSO connections to downstream servers). Must be called during server initialization, before the server starts handling requests.
The handler is only invoked when the token store implements storage.RefreshTokenFamilyStore. A warning is logged at registration time if the current store does not support families.
func (*Server) SetSessionRevocationHandler ¶ added in v0.2.82
func (s *Server) SetSessionRevocationHandler(handler SessionRevocationHandler)
SetSessionRevocationHandler sets a callback that fires when a token family is revoked (e.g., on logout). This lets consumers clean up per-session state. Must be called during server initialization, before the server starts handling requests.
func (*Server) SetTokenFamilyRevocationHandler
deprecated
added in
v0.2.81
func (s *Server) SetTokenFamilyRevocationHandler(handler TokenFamilyRevocationHandler)
SetTokenFamilyRevocationHandler is the old name for SetSessionRevocationHandler.
Deprecated: Use SetSessionRevocationHandler instead.
func (*Server) SetTokenRefreshHandler ¶ added in v0.2.88
func (s *Server) SetTokenRefreshHandler(handler TokenRefreshHandler)
SetTokenRefreshHandler sets a callback that fires synchronously after a provider token is refreshed (proactively near expiry, or reactively when an expired token is encountered during validation). This lets consumers react to refresh events immediately, eliminating the need for separate ID token caching layers. Must be called during server initialization, before the server starts handling requests.
The handler always fires on refresh events. However, userID and familyID are only populated when the token store implements storage.TokenMetadataGetter. A warning is logged at registration time if the current store does not support metadata lookups.
func (*Server) SetUserRateLimiter ¶
func (s *Server) SetUserRateLimiter(rl *security.RateLimiter)
SetUserRateLimiter sets the user-based rate limiter for authenticated requests
func (*Server) Shutdown ¶ added in v0.1.24
Shutdown gracefully shuts down the server and all its components. It stops rate limiters, closes storage connections, and cleans up resources. Safe to call multiple times - only the first call will execute shutdown.
The context parameter controls the shutdown timeout. If the context is cancelled or times out before shutdown completes, Shutdown returns the context error.
IMPORTANT: This method only stops background goroutines (rate limiters, cleanup tasks). It does NOT handle in-flight HTTP requests. For production deployments, you should:
- Stop accepting new connections (e.g., http.Server.Shutdown())
- Wait for in-flight requests to complete
- Call this method to clean up background processes
Recommended production shutdown sequence:
// Step 1: Stop accepting new requests (with timeout)
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
log.Printf("HTTP server shutdown error: %v", err)
}
// Step 2: Clean up OAuth server background processes
if err := oauthServer.Shutdown(shutdownCtx); err != nil {
log.Printf("OAuth server shutdown error: %v", err)
}
Simple example for non-production use:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
func (*Server) ShutdownWithTimeout ¶ added in v0.1.24
ShutdownWithTimeout is a convenience wrapper around Shutdown that creates a context with the specified timeout.
Example usage:
if err := server.ShutdownWithTimeout(30 * time.Second); err != nil {
log.Printf("Shutdown error: %v", err)
}
func (*Server) StartAuthorizationFlow ¶
func (s *Server) StartAuthorizationFlow(ctx context.Context, clientID, redirectURI, scope, resource, codeChallenge, codeChallengeMethod, clientState string, authOpts *providers.AuthorizationURLOptions) (string, error)
StartAuthorizationFlow starts a new OAuth authorization flow clientState is the state parameter from the client (REQUIRED for CSRF protection) resource is the target resource server identifier per RFC 8707 (optional for backward compatibility) authOpts contains optional OIDC parameters (prompt, login_hint, id_token_hint) for upstream IdP forwarding
func (*Server) TokenStore ¶ added in v0.1.41
func (s *Server) TokenStore() storage.TokenStore
TokenStore returns the token store used by the server. This allows the handler to access token metadata for scope validation.
func (*Server) ValidateClientCredentials ¶
func (s *Server) ValidateClientCredentials(ctx context.Context, clientID, clientSecret string) error
ValidateClientCredentials validates client credentials for token endpoint
func (*Server) ValidateRedirectURIAtAuthorizationTime ¶ added in v0.2.18
func (s *Server) ValidateRedirectURIAtAuthorizationTime(ctx context.Context, redirectURI string) error
ValidateRedirectURIAtAuthorizationTime performs security validation on a redirect URI during the authorization request. This is a secondary validation point that provides defense against TOCTOU (Time-of-Check to Time-of-Use) attacks.
This method is only called when Config.ValidateRedirectURIAtAuthorization=true.
Security context: The primary validation happens at client registration (ValidateRedirectURIForRegistration). However, DNS rebinding attacks can bypass registration-time validation: 1. Attacker registers with hostname "evil.com" resolving to public IP 1.2.3.4 2. After registration, attacker changes DNS to resolve to internal IP 10.0.0.1 3. Authorization request redirects to internal network (SSRF)
By re-validating at authorization time, we catch DNS rebinding attacks. The trade-off is additional latency for DNS lookups during authorization.
Note: This only applies security validation. The registered redirect_uri matching is still performed separately by validateRedirectURI().
func (*Server) ValidateRedirectURIForRegistration ¶ added in v0.2.18
ValidateRedirectURIForRegistration performs comprehensive security validation on a redirect URI during client registration. This is the primary entry point for redirect URI validation with full security controls.
This implements OAuth 2.0 Security BCP Section 4.1 and addresses: - SSRF attacks via private IP addresses - XSS attacks via dangerous schemes (javascript:, data:) - Open redirect vulnerabilities - Cloud metadata service access via link-local addresses
The validation is configurable via Config to support different deployment scenarios: - Production SaaS: Strict validation (default) - Internal/VPN: Allow private IPs - Development: Relaxed validation
func (*Server) ValidateRedirectURIsForRegistration ¶ added in v0.2.18
func (s *Server) ValidateRedirectURIsForRegistration(ctx context.Context, redirectURIs []string) error
ValidateRedirectURIsForRegistration validates multiple redirect URIs for client registration. Returns an error for the first invalid URI found.
func (*Server) ValidateToken ¶
func (s *Server) ValidateToken(ctx context.Context, accessToken string) (*providers.UserInfo, error)
ValidateToken validates an access token with local expiry check and provider validation. This implements defense-in-depth by checking token expiry locally BEFORE delegating to the provider, preventing expired tokens from being accepted due to clock skew.
Validation flow: 1. Check if token is a JWT with a trusted audience (SSO token forwarding) 2. If JWT with trusted audience, validate via JWKS signature verification 3. Check if token exists in local storage 4. If found, validate expiry locally (with ClockSkewGracePeriod) 5. RFC 8707: Validate audience binding (token intended for this resource server) 6. If expired locally, return error immediately (don't call provider) 7. Validate with provider (external check - userinfo endpoint) 8. Store updated user info
Note: Rate limiting should be done at the HTTP layer with IP address, not here with token
type SessionCreationHandler ¶ added in v0.2.82
SessionCreationHandler is called synchronously when a new token family is created during authorization code exchange. Consumers can use this to initialize per-session state (e.g., establish SSO connections).
The provided context is the HTTP request context of the token exchange and may be canceled when the request completes. If the handler performs slow initialization, it should derive a new context with its own deadline.
The handler is only invoked when the token store supports refresh token family tracking (implements storage.RefreshTokenFamilyStore). If the store does not support families, the handler is silently skipped.
Parameters:
- ctx: the HTTP request context of the token exchange
- userID: the authenticated user's identifier
- familyID: the newly created token family ID (used as session ID)
- token: the issued OAuth token (contains id_token in Extra for OIDC flows)
type SessionRevocationHandler ¶ added in v0.2.82
SessionRevocationHandler is called when a token family is revoked (e.g., on logout). Consumers can use this to clean up per-session state associated with the family ID.
The provided context is the HTTP request context that triggered the revocation and may be canceled when the request completes. If the handler performs slow cleanup operations, it should derive a new context with its own deadline.
type TokenFamilyRevocationHandler
deprecated
added in
v0.2.81
type TokenFamilyRevocationHandler = SessionRevocationHandler
TokenFamilyRevocationHandler is the old name for SessionRevocationHandler.
Deprecated: Use SessionRevocationHandler instead.
type TokenRefreshHandler ¶ added in v0.2.88
TokenRefreshHandler is called synchronously after a provider token is refreshed, either proactively (near-expiry) or reactively (expired token during validation). Consumers can use this to update downstream caches (e.g., ID token caches for SSO forwarding) without a separate polling layer.
The provided context is the HTTP request context that triggered the refresh. If the handler performs slow operations, it should derive a new context with its own deadline.
Parameters:
- ctx: the HTTP request context that triggered the refresh
- userID: the authenticated user's identifier
- familyID: the token family ID (session ID); empty if the store does not support families
- newToken: the freshly obtained provider token (contains id_token in Extra for OIDC flows)