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, err := google.NewProvider(&google.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
})
if err != nil {
log.Fatal(err)
}
store := memory.New()
config := &server.Config{
Issuer: "https://auth.example.com",
RequirePKCE: true,
}
// The same store backs the TokenStore, ClientStore, and FlowStore
// interfaces here because memory.Store implements all three.
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 GenerateRegistrationAccessToken() (plaintext, hash string, err error)
- func GetRedirectURIErrorCategory(err error) string
- func IsRedirectURISecurityError(err error) bool
- type AccessTokenClaims
- type AccessTokenFormat
- type AccessTokenIssuer
- type Actor
- type BrokeredExchangeRequest
- type CORSConfig
- type ClientMetadata
- type Config
- func (c *Config) AuthorizationEndpoint() string
- func (c *Config) ClientManagementEndpoint() string
- func (c *Config) GetResourceIdentifier() string
- func (c *Config) IntrospectionEndpoint() string
- func (c *Config) IsJWTAccessTokenFormat() bool
- func (c *Config) JWKSEndpoint() string
- func (c *Config) ProtectedResourceMetadataEndpoint() string
- func (c *Config) RegistrationEndpoint() string
- func (c *Config) RevocationEndpoint() string
- func (c *Config) SetTrustedRedirectURIsSet(uris []string)
- func (c *Config) SetTrustedSchemesMap(schemes []string)
- func (c *Config) TokenEndpoint() string
- func (c *Config) UserInfoEndpoint() string
- func (c *Config) Validate() error
- type Confirmation
- type DPoPNonceProvider
- type DPoPProofClaims
- type DPoPReplayCache
- type ExchangeOptions
- type Exchanger
- type ExchangerRequest
- type ExchangerResult
- type ForwardedIDTokenAcceptance
- type InterstitialBranding
- type InterstitialConfig
- type OIDCValidator
- type Option
- func WithAuditor(aud *security.Auditor) Option
- func WithClientRegistrationRateLimiter(rl *security.ClientRegistrationRateLimiter) Option
- func WithDPoPNonceProvider(provider DPoPNonceProvider) Option
- func WithDPoPReplayCache(cache DPoPReplayCache) Option
- func WithExchanger(e Exchanger) Option
- func WithInstrumentation(inst *instrumentation.Instrumentation) Option
- func WithMetadataFetchRateLimiter(rl *security.RateLimiter) Option
- func WithRateLimiter(rl *security.RateLimiter) Option
- func WithSecurityEventRateLimiter(rl *security.RateLimiter) Option
- func WithSessionCreationHandler(handler SessionCreationHandler) Option
- func WithSessionRevocationHandler(handler SessionRevocationHandler) Option
- func WithSubjectTokenValidator(tokenType string, v SubjectTokenValidator) Option
- func WithTokenRefreshHandler(handler TokenRefreshHandler) Option
- func WithTrustedAudiences(audiences []string) Option
- func WithTrustedIssuers(issuers []TrustedIssuer) Option
- func WithTrustedProxyCIDRs(cidrs []*net.IPNet) Option
- func WithUserRateLimiter(rl *security.RateLimiter) Option
- type ProtectedResourceConfig
- type RedirectURISecurityError
- type RegisterClientResult
- type SelfIssuedExchangeRequest
- type Server
- func (s *Server) AcceptForwardedIDToken(ctx context.Context, bearerToken string) (*ForwardedIDTokenAcceptance, error)
- func (s *Server) AcceptTrustedIssuerToken(ctx context.Context, bearerToken string) (*ForwardedIDTokenAcceptance, error)
- func (s *Server) BrokeredExchange(ctx context.Context, req BrokeredExchangeRequest) (*TokenExchangeResult, error)
- func (s *Server) CanRegisterWithTrustedRedirectURI(redirectURIs []string) (allowed bool, matchedURI string, err error)
- func (s *Server) CanRegisterWithTrustedScheme(redirectURIs []string) (allowed bool, scheme string, err error)
- func (s *Server) DPoPNonceProvider() DPoPNonceProvider
- func (s *Server) DPoPReplayCache() DPoPReplayCache
- func (s *Server) DeleteClient(ctx context.Context, clientID string) error
- func (s *Server) EnsureConfidentialClient(ctx context.Context, clientID, clientSecret string, scopes []string) (seeded bool, err error)
- func (s *Server) ExchangeAuthorizationCode(ctx context.Context, ...) (*oauth2.Token, string, error)
- func (s *Server) Exchanger() Exchanger
- 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) IntrospectToken(ctx context.Context, accessToken, requestingClient string) map[string]any
- func (s *Server) LogValue() slog.Value
- func (s *Server) PublicJWKS() (*jose.JSONWebKeySet, error)
- func (s *Server) RefreshAccessToken(ctx context.Context, refreshToken, clientID string) (*oauth2.Token, error)
- func (s *Server) RefreshSession(ctx context.Context, familyID string) (*oauth2.Token, error)
- func (s *Server) RegisterClient(ctx context.Context, clientName, clientType, tokenEndpointAuthMethod string, ...) (*storage.Client, string, error)
- func (s *Server) RegisterClientV2(ctx context.Context, clientName, clientType, tokenEndpointAuthMethod string, ...) (*RegisterClientResult, 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) SaveClient(ctx context.Context, client *storage.Client) error
- func (s *Server) SelfIssuedExchange(ctx context.Context, req SelfIssuedExchangeRequest) (*TokenExchangeResult, error)
- func (s *Server) SessionIDForBearer(bearerToken string) string
- func (s *Server) Shutdown(ctx context.Context) error
- func (s *Server) ShutdownWithTimeout(timeout time.Duration) error
- func (s *Server) StartAuthorizationFlow(ctx context.Context, clientID string, redirectURI *url.URL, ...) (string, error)
- func (s *Server) SubjectValidatorFor(tokenType string) SubjectTokenValidator
- func (s *Server) TokenStore() storage.TokenStore
- func (s *Server) TrustedProxyCIDRs() []*net.IPNet
- func (s *Server) ValidateClientCredentials(ctx context.Context, clientID, clientSecret string) error
- func (s *Server) ValidateRedirectURIAtAuthorizationTime(ctx context.Context, redirectURI string) error
- func (s *Server) ValidateRedirectURIForAuthorization(ctx context.Context, clientID, redirectURI string) (*url.URL, 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 SubjectExchange
- type SubjectIdentity
- type SubjectTokenValidator
- type TokenExchangeResult
- type TokenExchangeUnsupportedTypeError
- type TokenRefreshHandler
- type TrustedIssuer
- type TypedToken
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 ( SigningAlgorithmRS256 = "RS256" SigningAlgorithmRS384 = "RS384" SigningAlgorithmRS512 = "RS512" SigningAlgorithmES256 = "ES256" SigningAlgorithmES384 = "ES384" )
JWT signing algorithms accepted in AccessTokenFormatJWT mode. The set is deliberately closed: HMAC variants are rejected because they require the resource server to share a symmetric secret with the issuer, defeating the "publish a JWKS, verify locally" model. "none" is rejected because it is not signing.
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" // EndpointPathClientManagement is the per-client management base path (RFC 7592). // The actual routes are GET/PUT/DELETE /oauth/register/{client_id}. EndpointPathClientManagement = "/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" // EndpointPathUserInfo is the OIDC UserInfo endpoint path (OIDC Core 1.0 §5.3). // Returns claims about the authenticated user, gated by the access token's // granted scopes (openid required; profile/email/groups gate corresponding claims). EndpointPathUserInfo = "/oauth/userinfo" // EndpointPathProtectedResourceMetadata is the Protected Resource Metadata discovery path (RFC 9728) EndpointPathProtectedResourceMetadata = "/.well-known/oauth-protected-resource" // EndpointPathJWKS is the JSON Web Key Set discovery path (RFC 7517). // In AccessTokenFormatJWT mode the server publishes the public half of the // access-token signing key here so downstream resource servers can verify // bearers locally. In AccessTokenFormatOpaque mode the endpoint returns 404. EndpointPathJWKS = "/.well-known/jwks.json" )
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 = constants.ErrorCodeInvalidClient ErrorCodeInvalidRequest = constants.ErrorCodeInvalidRequest ErrorCodeInvalidRedirectURI = constants.ErrorCodeInvalidRedirectURI ErrorCodeInvalidScope = constants.ErrorCodeInvalidScope ErrorCodeInvalidGrant = constants.ErrorCodeInvalidGrant OAuthSpecVersion = constants.OAuthSpecVersion )
OAuth 2.0 / 2.1 error codes and spec version re-exported from internal/constants so that server-package code can reference them without a circular import.
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 ( SubjectTokenTypeIDToken = "urn:ietf:params:oauth:token-type:id_token" // #nosec G101 -- RFC 8693 token-type URN identifier, not a credential SubjectTokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token" // #nosec G101 -- RFC 8693 token-type URN identifier, not a credential SubjectTokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt" // #nosec G101 -- RFC 8693 token-type URN identifier, not a credential )
Token-type URN constants shared between OIDCValidator and the token-exchange handler.
const ( MinCodeVerifierLength = constants.MinCodeVerifierLength MaxCodeVerifierLength = constants.MaxCodeVerifierLength PKCEMethodS256 = constants.PKCEMethodS256 PKCEMethodPlain = constants.PKCEMethodPlain )
PKCE validation constants (RFC 7636).
const ( // DPoPSupportedAlgs is the space-separated list of accepted DPoP proof // signature algorithms, used in WWW-Authenticate challenges (RFC 9449 §7.1). DPoPSupportedAlgs = "RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512" )
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 GrantTypeTokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange" // #nosec G101 -- RFC 8693 grant-type URN identifier, not a credential
GrantTypeTokenExchange is the grant_type value for RFC 8693 token exchange.
const MaxResourceLength = constants.MaxResourceLength
MaxResourceLength is the maximum allowed length for the resource parameter (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 )
Variables ¶
var ( // AllowedHTTPSchemes lists the allowed HTTP(S) redirect URI schemes. AllowedHTTPSchemes = constants.AllowedHTTPSchemes // DefaultBlockedRedirectSchemes is the canonical list of URI schemes that // must never appear in redirect URIs. DefaultBlockedRedirectSchemes = constants.DefaultBlockedRedirectSchemes // DangerousSchemes is an alias for DefaultBlockedRedirectSchemes. // // Deprecated: Use DefaultBlockedRedirectSchemes instead. DangerousSchemes = constants.DefaultBlockedRedirectSchemes // DefaultRFC3986SchemePattern is the regex that validates custom URI schemes (RFC 3986 §3.1). DefaultRFC3986SchemePattern = constants.DefaultRFC3986SchemePattern )
var ErrDPoPNonceInvalid = errors.New("dpop: nonce required or stale")
ErrDPoPNonceInvalid is returned by ValidateDPoPProof when the server requires a nonce and the proof's nonce claim is absent or does not match a currently accepted value (RFC 9449 §8). Callers must respond with error=use_dpop_nonce and a DPoP-Nonce response header carrying a fresh nonce.
var ErrExchangeRateLimited = errors.New("token exchange rate limit exceeded")
ErrExchangeRateLimited is returned by BrokeredExchange when the configured UserRateLimiter rejects the request. The HTTP handler rate-limits authenticated requests in middleware, but the method may also be called in-process, so the same limiter is enforced here keyed on the per-session ID. Callers invoking the method directly should surface this as a 429-equivalent.
var ErrInvalidTarget = errors.New("requested audience is not allowed for this client")
ErrInvalidTarget is returned by BrokeredExchange when the requested audience cannot be served: no Exchanger is configured, the client's allowlist does not contain the audience, or the Exchanger itself reports the audience as unknown. Handlers map it to the RFC 8693 §2.2.2 invalid_target error response.
var ErrIssuerNotTrusted = errors.New("untrusted issuer")
ErrIssuerNotTrusted is returned by Validate when the token is not a JWT, has no iss claim, or its iss is not in the configured issuer set. Resource-server callers may use errors.Is to fall through to other validation paths.
var ErrMissingIssuer = errors.New("issuer is required")
ErrMissingIssuer is returned by Config.Validate when Config.Issuer is empty (RFC 8414 §2).
var ErrSelfRenewalDenied = errors.New("self-issued token cannot be re-exchanged without an actor")
ErrSelfRenewalDenied is returned by SelfIssuedExchange when a token this server issued is re-presented as the subject with no new actor. That is a bare renewal: it refreshes the TTL without extending the delegation chain, which would let a held token renew itself indefinitely. Re-exchange a self-issued token only to add an actor (a delegation hop, bounded by maxActorChainDepth).
var ErrSubjectKeyMismatch = errors.New("subject token is key-bound; request must prove possession of its confirmation key")
ErrSubjectKeyMismatch is returned by SelfIssuedExchange when the subject token carries an RFC 9449 §6.1 cnf.jkt proof-of-possession binding but the request does not present a DPoP proof for that same key. A key-bound token may only be re-exchanged by the holder of its confirmation key, so a bearer who lacks the key cannot launder the binding into a token bound to a different key.
var ErrTrustedAudienceMismatch = errors.New("forwarded ID token audience does not match TrustedAudiences")
ErrTrustedAudienceMismatch is returned by Server.AcceptForwardedIDToken when the bearer token's `aud` claim does not match any entry in Config.TrustedAudiences. Callers should typically respond with 401.
var ErrUnverifiedSubjectEmail = errors.New("subject email_verified is not true; refusing to issue a token asserting an unproven email")
ErrUnverifiedSubjectEmail is returned by SelfIssuedExchange when the validated subject asserts an email claim without email_verified being true. The issued token would carry that email as an identity claim which downstream resource servers authorize on, so issuance fails closed. A subject with no email claim (e.g. a Kubernetes ServiceAccount token) is exempt.
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 GenerateRegistrationAccessToken ¶ added in v0.2.145
GenerateRegistrationAccessToken mints a high-entropy per-client registration access token (RFC 7592) and returns the plaintext and its bcrypt hash.
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 AccessTokenClaims ¶ added in v0.2.120
type AccessTokenClaims struct {
// Subject is the authenticated user identifier (RFC 9068 sub claim).
Subject string
// ClientID is the OAuth client this token was issued to (RFC 9068
// client_id claim).
ClientID string
// Audience is the resource server identifier this token is bound to
// per RFC 8707. Becomes the JWT aud claim and is enforced at validation
// time against Config.GetResourceIdentifier().
Audience string
// Scopes are the granted scopes; serialized space-delimited into the
// scope claim per RFC 9068 §2.2.3. Empty slice produces no claim.
Scopes []string
// Email is the verified user email when present from the upstream
// provider's UserInfo, included for downstream identity-attributed audit
// logs without an extra IdP round-trip.
Email string
// EmailVerified mirrors providers.UserInfo.EmailVerified. Emitted as the
// email_verified claim only when Email is non-empty, so that introspection
// of a JWT access token (RFC 7662 §2.2) projects the same identity
// attributes as the opaque branch.
EmailVerified bool
// Name mirrors providers.UserInfo.Name. Emitted as the OIDC standard name
// claim when non-empty; same parity rationale as EmailVerified.
Name string
// Groups are the user's group memberships from the upstream provider.
// Included verbatim under the "groups" claim when non-empty.
Groups []string
// IssuedAt and ExpiresAt define the validity window. The issuer copies
// IssuedAt into iat and ExpiresAt into exp; ExpiresAt must be set to a
// non-zero time in the future or validation will reject the token.
IssuedAt time.Time
ExpiresAt time.Time
// JTI is an optional caller-supplied unique identifier. When empty the
// issuer generates one. Exported so a caller running its own jti
// allocation (e.g. correlated with a trace ID) can inject it.
JTI string
// FamilyID is the OAuth 2.1 refresh-token family identifier this access
// token belongs to (storage.RefreshTokenFamilyMetadata.FamilyID). When
// non-empty it is written into the JWT as the family_id claim and
// checked at validation time against RefreshTokenFamilyStore — revoking
// the family invalidates all in-flight access tokens in the family,
// not only future issuance.
//
// Empty when the underlying token store does not implement
// RefreshTokenFamilyStore.
FamilyID string
// Act carries the RFC 8693 §4.4 actor claim. Non-nil only for token-exchange
// issued tokens.
Act *Actor
// JKT is the JWK thumbprint of the DPoP public key this token is bound to
// (RFC 9449 §6.1). Non-empty only when the token was requested with a DPoP proof.
JKT string
// Extra holds application-defined claims merged into the JWT body verbatim
// after the standard claims. Keys must not collide with RFC 7519 §4.1
// registered claim names (iss, sub, aud, exp, nbf, iat, jti) — Issue
// returns an error if they do. OIDC-profile claims (email, name, groups,
// email_verified) set via struct fields are not guarded and can be
// overridden by Extra. Nil is a no-op.
Extra map[string]any
}
AccessTokenClaims is the input passed to AccessTokenIssuer.Issue. It carries the OAuth-flow context the issuer needs to construct the bearer string.
The shape is provider-agnostic: opaque issuers ignore most of these fields and just generate a 256-bit random string, while JWT issuers fold them into signed claims per RFC 9068 §2.2.
type AccessTokenFormat ¶ added in v0.2.120
type AccessTokenFormat string
AccessTokenFormat selects how the server encodes access tokens.
Use AccessTokenFormatOpaque (the default) to issue 256-bit random strings keyed into the TokenStore. Use AccessTokenFormatJWT to issue signed JWTs per RFC 9068 ("JWT Profile for OAuth 2.0 Access Tokens"); downstream resource servers can then verify bearers locally against the published JWKS without per-request introspection round-trips.
const ( // AccessTokenFormatOpaque issues 256-bit random strings as access tokens. // Every bearer is a database key into TokenStore; validation requires // a TokenStore lookup followed by a provider userinfo round-trip. This // is the default — existing deployments behave identically when // AccessTokenFormat is left empty. AccessTokenFormatOpaque AccessTokenFormat = "opaque" // AccessTokenFormatJWT issues signed JWTs as access tokens per RFC 9068. // Validation is local: signature verification against the public key plus // claim checks (iss, exp, aud, jti). Downstream resource servers fetch // the public key from the server's JWKS endpoint. AccessTokenFormatJWT AccessTokenFormat = "jwt" )
type AccessTokenIssuer ¶ added in v0.2.120
type AccessTokenIssuer interface {
// Issue produces a bearer string for the given claims. The returned
// string is what the client receives in the access_token field of the
// token response and what it sends back as a Bearer credential.
Issue(ctx context.Context, c AccessTokenClaims) (string, error)
}
AccessTokenIssuer encodes AccessTokenClaims into a bearer string. Two implementations exist:
- opaqueIssuer: generates a 256-bit random string keyed into TokenStore.
- jwtIssuer: signs a JWT per RFC 9068 ("JWT Profile for OAuth 2.0 Access Tokens") with the configured key, kid, and algorithm.
The Server holds exactly one AccessTokenIssuer for its lifetime, selected at construction from Config.AccessTokenFormat.
type Actor ¶ added in v0.2.150
type Actor struct {
Iss string `json:"iss,omitempty"`
Sub string `json:"sub,omitempty"`
Act *Actor `json:"act,omitempty"`
}
Actor is the RFC 8693 §4.4 act claim: the acting party in a delegation chain. Act nests the prior actor so a multi-hop A2A chain (human → agentA → agentB) is preserved: the outermost Act is the most recent actor, Act.Act the one before it. Nil at the end of the chain.
type BrokeredExchangeRequest ¶ added in v1.0.1
type BrokeredExchangeRequest struct {
SubjectExchange
ClientID string
Audience string
}
BrokeredExchangeRequest is the input to BrokeredExchange, where the token is signed by a downstream issuer reached through the host Exchanger. ClientID is the authenticated confidential client; Audience is the RFC 8693 logical target the Exchanger resolves to a downstream issuer.
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 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)
// AccessTokenFormat selects how the server encodes access tokens.
//
// AccessTokenFormatOpaque (default) — bearers are 256-bit random strings
// keyed into TokenStore; ValidateToken calls the upstream provider's
// userinfo endpoint on every request. Compatible with all providers
// (including GitHub OAuth Apps which do not issue JWTs upstream).
//
// AccessTokenFormatJWT — bearers are signed JWTs per RFC 9068. Downstream
// resource servers can verify locally against the published JWKS.
// Requires AccessTokenSigningKey, AccessTokenSigningKeyID, and
// AccessTokenSigningAlgorithm to be set.
//
// Default: "" (treated as AccessTokenFormatOpaque). Existing deployments
// are unaffected.
AccessTokenFormat AccessTokenFormat
// AccessTokenSigningKey is the private key used to sign access tokens in
// AccessTokenFormatJWT mode. Must be a *rsa.PrivateKey or *ecdsa.PrivateKey
// whose type matches AccessTokenSigningAlgorithm (RSA keys for RS*, ECDSA
// keys for ES*).
//
// Operators should load this from a mounted secret or KMS, never from
// source. Required when AccessTokenFormat is AccessTokenFormatJWT;
// ignored otherwise.
AccessTokenSigningKey crypto.Signer
// AccessTokenSigningKeyID is the JWK "kid" parameter advertised in JWT
// headers and the JWKS endpoint. Resource servers use this to pick the
// right verification key when the issuer publishes more than one. Must
// be a stable, non-empty identifier — rotating the key in operator
// configuration without changing the kid will silently break in-flight
// tokens whose verifiers cached the old material.
//
// Required when AccessTokenFormat is AccessTokenFormatJWT.
AccessTokenSigningKeyID string
// AccessTokenSigningAlgorithm is the JWS "alg" used to sign access tokens.
// Closed set: RS256, RS384, RS512, ES256, ES384. HMAC variants and
// "none" are rejected at construction time — HMAC defeats the
// publish-a-JWKS-and-verify-locally model, and "none" is not signing.
//
// Must match the type of AccessTokenSigningKey (RS* requires RSA, ES*
// requires ECDSA on the matching curve). Required when AccessTokenFormat
// is AccessTokenFormatJWT.
AccessTokenSigningAlgorithm string
// 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
// 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)
// DiscoveryCacheMaxAge is the max-age advertised on Cache-Control for the
// three discovery endpoints (/.well-known/oauth-authorization-server,
// /.well-known/oauth-protected-resource, /.well-known/openid-configuration)
// per RFC 8414 §3 and RFC 9728 §3.
// Zero or negative selects the 1h default. Set to a very small value to
// effectively disable intermediary caching.
DiscoveryCacheMaxAge time.Duration // default: 1h
// 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
// MaxStateLength caps the `state` parameter length to prevent audit-log
// inflation / DoS via oversized state values.
// Default: 512 characters (accommodates common JWT-encoded-state payloads).
MaxStateLength int // default: 512
// 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
// TrustedPublicRegistrationRedirectURIs lists fully-qualified HTTPS redirect URIs
// allowed to register without a RegistrationAccessToken. Matching is exact after
// RFC 3986 normalization: scheme and host are lowercased, the HTTPS default port
// (:443) is stripped, and trailing slashes are stripped from the path. Path and
// query are then compared case-sensitively. All redirect URIs in a registration
// request must be in this set; otherwise the token gate applies.
//
// Entries must be HTTPS, must not contain a fragment, userinfo, or a loopback,
// private, link-local, or unspecified IP literal host. Invalid entries are dropped
// at configuration time.
//
// Default: [].
TrustedPublicRegistrationRedirectURIs []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
// JWKSRootCAs is the CA pool used to verify the JWKS endpoint's TLS
// certificate when validating forwarded SSO ID tokens (TrustedAudiences)
// against an internal-CA IdP (e.g. an internal Dex whose certificate is
// signed by a private CA). nil uses the system pool.
//
// The consuming process must build and pass this pool explicitly; mcp-oauth
// no longer reads a CA installed on http.DefaultTransport. Typically set
// alongside AllowPrivateIPJWKS for a private-IP, internal-CA IdP.
JWKSRootCAs *x509.CertPool
// 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 overrides the resolver used for DNS lookups during redirect URI
// validation. If nil, net.DefaultResolver is used.
DNSResolver *net.Resolver
// 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
// 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
// TokenExchangeClientAudiences is the per-client allowlist for the
// brokered RFC 8693 token-exchange flow. Keys are client IDs; values are
// the audiences each client may request via the `audience` parameter.
// A client requesting an audience not in its list receives invalid_target
// (RFC 8693 §2.2.2).
//
// Only consulted when an Exchanger is configured (server.WithExchanger).
// Entries should reference confidential clients only — the broker path
// rejects public clients because an unauthenticated client_id would make
// the allowlist spoofable.
//
// Default: nil (no client may request any audience).
TokenExchangeClientAudiences map[string][]string
// TokenExchangeAllowedResources caps the RFC 8707 resource values that the
// self-issued RFC 8693 flow (Server.SelfIssuedExchange) will mint a token
// for. When non-empty, a request whose resource is not in this list is
// rejected with invalid_target (RFC 8693 §2.2.2). The server's own
// ResourceIdentifier is always permitted, so a request that omits resource
// (audience defaults to GetResourceIdentifier) is always served.
//
// Default: nil (any resource is accepted; the issued token's aud is the
// requested resource, or the server's ResourceIdentifier when omitted).
TokenExchangeAllowedResources []string
// SessionIDHMACKey optionally replaces the default SHA-256 session-ID derivation
// in Server.AcceptForwardedIDToken with HMAC-SHA-256 keyed by this value.
//
// Session IDs have the form "ext-<16 hex chars>". The digest input is
// domain-separated with a versioned label so the key is safely reusable for
// other keyed derivations without cross-purpose collision risk; see the
// Server.AcceptForwardedIDToken godoc for the exact construction.
//
// Default (nil/empty): unkeyed SHA-256 over the domain-separated input.
// Deterministic across deployments receiving the same token — this gives
// cross-hop audit-log correlation when an aggregator (e.g. muster) fans a
// single forwarded token out to multiple downstream MCP servers. Intended
// for single-tenant deployments.
//
// Trade-off: anyone with audit-log access across two servers can link a user's
// sessions across them via the token hash. For multi-tenant isolation, set this key
// to a per-deployment secret — cross-hop correlation then holds only among servers
// sharing the same key.
//
// Operator caveat: every MCP server in a correlation set must either all configure
// the same key or all leave it empty. A single mismatched key silently breaks
// correlation with no runtime error — the tokens still validate, the sessions just
// no longer line up across hops. The library cannot detect the mismatch.
//
// Recommended: 32 random bytes from crypto/rand, loaded from a secret
// manager or mounted file (not from an environment variable).
SessionIDHMACKey []byte
// RequireNonceEcho enforces upstream id_token `nonce` claim matching on the
// authorization-code callback. Derived from DisableNonceEchoRequirement
// during config validation — operators should toggle the Disable* knob and
// leave this field alone (same X / DisableX pattern as
// ValidateRedirectURIAtAuthorization).
RequireNonceEcho bool
// DisableNonceEchoRequirement opts out of the upstream id_token `nonce`
// echo check.
DisableNonceEchoRequirement bool
// 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
// IntrospectionResourceServers lists client IDs permitted to introspect
// tokens they do not own. Empty means strict same-client gating.
//
// SECURITY: an allowlisted resource server receives the full RFC 7662 §2.2
// projection for any token issued to in-scope clients — including `sub`,
// `email`, `name`, `scope`, and `aud`. Allowlist only services that are
// already authorized to learn those attributes; this knob is not a
// substitute for token-binding or DPoP if the goal is to gate on the
// presenter rather than on the token's recipient.
IntrospectionResourceServers []string
// EnableUserInfoEndpoint controls whether the OIDC UserInfo endpoint
// (OIDC Core 1.0 §5.3) is mounted and advertised. When true:
// - /oauth/userinfo is registered (RegisterOAuthRoutes).
// - `userinfo_endpoint` is included in the AS / OIDC discovery metadata.
// Requires bearer-token-protected requests with the `openid` scope; the
// `profile`, `email`, and `groups` scopes gate corresponding claims.
// Default: false (opt-in to avoid exposing user data without operator consent).
EnableUserInfoEndpoint bool
// EnableClientManagementEndpoint controls whether RFC 7592 client management
// routes (GET/PUT/DELETE /oauth/register/{client_id}) are mounted. When true:
// - management routes are registered (RegisterOAuthRoutes).
// - `registration_management_endpoint` is included in AS metadata (RFC 8414 §2).
// - `registration_access_token` and `registration_client_uri` are included in
// new DCR responses (RFC 7591 §3.2.1).
// Default: false.
EnableClientManagementEndpoint 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) ClientManagementEndpoint ¶ added in v0.2.145
ClientManagementEndpoint returns the base URL for the RFC 7592 client management endpoint. Individual client endpoints append "/{client_id}".
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) IsJWTAccessTokenFormat ¶ added in v0.2.120
IsJWTAccessTokenFormat reports whether the server should issue JWTs as access tokens. Empty AccessTokenFormat is treated as opaque so existing deployments remain unaffected.
func (*Config) JWKSEndpoint ¶ added in v0.2.120
JWKSEndpoint returns the full URL to the JWKS discovery endpoint (RFC 7517). Advertised in Authorization Server Metadata only when AccessTokenFormat is AccessTokenFormatJWT.
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) SetTrustedRedirectURIsSet ¶ added in v0.2.125
SetTrustedRedirectURIsSet builds the pre-computed trusted redirect URI set from the given URIs. URIs are normalized per RFC 3986 (lowercase scheme + host, default port stripped). Invalid entries are skipped.
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
func (*Config) UserInfoEndpoint ¶ added in v0.2.134
UserInfoEndpoint returns the full URL to the OIDC Core 1.0 §5.3 UserInfo endpoint.
func (*Config) Validate ¶ added in v0.2.120
Validate enforces invariants that cannot be expressed in the type system. It is called by New after [applySecureDefaults] and returns a non-nil error if the configuration would produce a server that is unsafe to run.
Currently this checks AccessTokenFormat consistency: in JWT mode the signing key, kid, and algorithm must all be set, the algorithm must be in the closed accepted set, and the algorithm must match the key type (RS* with RSA, ES* with ECDSA on the matching curve). All other fields are validated at construction time via applySecureDefaults / the dedicated validate* helpers.
type Confirmation ¶ added in v0.2.150
type Confirmation struct {
JKT string `json:"jkt,omitempty"`
}
Confirmation is the RFC 9449 §6.1 cnf claim: proof-of-possession key binding.
type DPoPNonceProvider ¶ added in v0.2.150
type DPoPNonceProvider interface {
// Nonce returns the current server-issued nonce value to include in a
// DPoP-Nonce response header. Callers return this value to the client
// whenever a use_dpop_nonce error is issued.
Nonce(ctx context.Context) string
// Valid reports whether nonce is currently accepted. Implementations SHOULD
// accept at least the current and one previous time window so that clients
// transitioning at a window boundary are not spuriously rejected.
Valid(ctx context.Context, nonce string) bool
}
DPoPNonceProvider generates and validates server-issued DPoP nonces (RFC 9449 §8). Implementations must be safe for concurrent use.
Set a provider via WithDPoPNonceProvider. When nil, ValidateDPoPProof accepts proofs that omit the nonce claim entirely.
func NewHMACNonceProvider ¶ added in v0.2.150
func NewHMACNonceProvider(secret []byte, window time.Duration, now func() time.Time) DPoPNonceProvider
NewHMACNonceProvider returns a DPoPNonceProvider that derives nonces with HMAC-SHA256 keyed on secret and rotated every window. Callers that do not need a testable clock should pass nil for now (time.Now is used).
A window of 10 minutes is a reasonable default: short enough to limit the replay exposure on a stolen nonce, long enough to tolerate mobile clients with slow network round-trips.
type DPoPProofClaims ¶ added in v0.2.150
type DPoPProofClaims struct {
// JKT is the JWK thumbprint of the public key that signed the proof.
JKT string
}
DPoPProofClaims holds the validated output of a DPoP proof JWT. JKT is the only field callers need: it is the JWK thumbprint to bind into the issued access token cnf claim.
func ValidateDPoPProof ¶ added in v0.2.150
func ValidateDPoPProof(ctx context.Context, proof, method, uri, accessToken string, replay DPoPReplayCache, nonces DPoPNonceProvider, now time.Time) (*DPoPProofClaims, error)
ValidateDPoPProof parses and validates a DPoP proof JWT per RFC 9449 §4.3. method and uri are the HTTP method and URI of the current request. accessToken is non-empty only at the resource server (enables ath check). nonces is optional: when non-nil, the proof must carry a currently valid server-issued nonce (RFC 9449 §8); a missing or stale nonce returns ErrDPoPNonceInvalid so callers can respond with use_dpop_nonce.
Nonce check occurs before replay recording: a proof rejected for a bad nonce does not consume its JTI in the replay cache.
type DPoPReplayCache ¶ added in v0.2.150
type DPoPReplayCache interface {
// Seen returns true if jti was already seen within the TTL window,
// and records it for future calls. An error means the cache is
// unavailable; callers should reject the proof on error.
Seen(ctx context.Context, jti string, ttl time.Duration) (bool, error)
}
DPoPReplayCache tracks seen DPoP proof JTIs to prevent replay attacks. Implementations must be safe for concurrent use.
func NewMemoryDPoPReplayCache ¶ added in v0.2.150
func NewMemoryDPoPReplayCache() DPoPReplayCache
NewMemoryDPoPReplayCache returns an in-memory replay cache. Expired entries are evicted lazily on each Seen call.
type ExchangeOptions ¶ added in v0.2.183
type ExchangeOptions struct {
Email string
// EmailVerified indicates whether Email has been verified by the identity
// source. It is emitted as email_verified whenever Email is non-empty, so a
// zero value emits false. SelfIssuedExchange defaults it from the subject only
// together with Email (when Options.Email is empty): a caller that sets Email
// explicitly but leaves EmailVerified false gets false even if the subject was
// verified, so set it explicitly alongside an explicit Email.
EmailVerified bool
Name string
Groups []string
Extra map[string]any
}
ExchangeOptions carries optional identity claims to inject into the access token issued by SelfIssuedExchange.
String and slice fields are omitted from the JWT when empty. EmailVerified is emitted only when Email is also non-empty (consistent with AccessTokenClaims semantics). Extra is merged into the JWT body after the standard claims; RFC 7519 §4.1 registered claim names (iss, sub, aud, exp, nbf, iat, jti) are rejected: Issue returns an error if Extra contains any of them. OIDC profile claims set via struct fields (email, name, groups, email_verified) are not guarded and can be overridden by Extra.
type Exchanger ¶ added in v0.3.0
type Exchanger interface {
Exchange(ctx context.Context, req *ExchangerRequest) (*ExchangerResult, error)
}
Exchanger maps a requested audience to a downstream token. Hosts (e.g. an MCP aggregator acting as a token broker) implement it to perform the downstream exchange — typically an RFC 8693 request against a remote issuer using host-held credentials. mcp-oauth stays generic: it owns subject-token validation, allowlist policy, and audit; the host owns the audience→issuer mapping.
Returning an error wrapping ErrInvalidTarget signals that the audience is unknown to the host; any other error is reported to the client as a generic invalid_grant without leaking detail.
type ExchangerRequest ¶ added in v0.3.0
type ExchangerRequest struct {
// Audience is the RFC 8693 audience parameter: the logical name of the
// downstream target the client wants a token for. The host maps it to a
// downstream issuer and credentials.
Audience string
// Resource is the RFC 8707 resource parameter, when the client supplied
// one alongside audience. May be empty.
Resource string
// Scope is the raw requested scope string. May be empty.
Scope string
// ClientID is the authenticated broker client that issued the request.
ClientID string
// Subject is the verified identity extracted from the subject token.
Subject *SubjectIdentity
// SubjectToken is the raw subject token. Hosts typically forward it
// unchanged as the subject_token of their own downstream exchange.
SubjectToken string
// SubjectTokenType is the RFC 8693 token-type URN of SubjectToken.
SubjectTokenType string
// ActorToken is the raw RFC 8693 actor_token, forwarded unchanged.
// Empty when no actor_token was presented.
ActorToken string
// ActorTokenType is the RFC 8693 token-type URN of ActorToken.
// Empty when ActorToken is empty.
ActorTokenType string
// Actor is the verified identity of the acting party (RFC 8693 §4.4
// delegation chain). Nil when no actor_token was presented.
Actor *SubjectIdentity
}
ExchangerRequest carries the validated inputs of a brokered RFC 8693 token exchange to the host's Exchanger implementation. The subject and actor tokens have already been validated (signature, issuer, audience, expiry) against the server's trusted issuers before the Exchanger is invoked. ClientID is the authenticated broker client that issued the request.
type ExchangerResult ¶ added in v0.3.0
type ExchangerResult struct {
// AccessToken is the downstream token returned to the client unchanged.
AccessToken string
// IssuedTokenType is the RFC 8693 issued_token_type URN. Empty defaults
// to urn:ietf:params:oauth:token-type:access_token.
IssuedTokenType string
// ExpiresAt is the downstream token's expiry. It bounds the expires_in
// reported to the client — brokered tokens never outlive the downstream
// token, and no refresh token is issued; clients re-exchange instead.
ExpiresAt time.Time
// Scope is the scope granted by the downstream issuer. May be empty.
Scope string
}
ExchangerResult is the downstream token returned by an Exchanger.
type ForwardedIDTokenAcceptance ¶ added in v0.2.102
type ForwardedIDTokenAcceptance struct {
// SessionID is a deterministic identifier of the form "ext-<16 hex chars>"
// derived from a domain-separated hash of the bearer token:
// default: first sessionIDDigestBytes of sha256(forwardedSessionIDLabel || 0x00 || token)
// with Config.SessionIDHMACKey set: same input, HMAC-SHA-256 with the key
//
// Two MCP servers receiving the same token compute the same SessionID, which
// gives cross-hop audit-log correlation when an aggregator fans a single
// forwarded token out to multiple downstream servers. See Config.SessionIDHMACKey
// for the isolation escape hatch and the operator caveat around key agreement.
//
// Scope: this is a correlation identifier for logs, metrics, and session
// caches — not a security boundary. The 64-bit truncation is sized for
// audit correlation, not collision resistance under adversarial input.
// Do not use SessionID as an authorization key, capability handle, or any
// identifier whose uniqueness a security decision depends on; the Subject
// field (or a store keyed on verified claims) is the correct source for
// authorization decisions.
SessionID string
// Subject is the validated `sub` claim from the JWT. It equals UserInfo.ID and
// is surfaced as a top-level field for callers that only need the subject.
Subject string
// UserInfo carries the other validated claims (email, name, groups, etc.) and
// has TokenSource = TokenSourceSSO.
UserInfo *providers.UserInfo
// Issuer is the validated `iss` claim from the JWT. Equals the configured
// provider's IssuerURL() — the signature check enforces that equality, so
// this field never carries an unverified value.
Issuer string
// Audience is the entry of Config.TrustedAudiences that matched the token's
// `aud` claim (not the raw aud claim itself, which may contain multiple
// values). Suitable for metric labels and audit records.
Audience string
// ExpiresAt is the JWT `exp` claim, for caller-side session-cache TTLs. The
// library does not refresh forwarded tokens — when a token expires, the caller
// must propagate 401 so the MCP client re-authenticates.
ExpiresAt time.Time
}
ForwardedIDTokenAcceptance is the verified result of accepting a JWT forwarded from a trusted upstream identity provider. It is returned by Server.AcceptForwardedIDToken and is safe to use for downstream routing, session keying, and audit-log correlation.
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 OIDCValidator ¶ added in v0.2.149
type OIDCValidator struct {
// contains filtered or unexported fields
}
OIDCValidator validates JWTs from statically configured trusted issuers. The unverified iss claim routes the token to its TrustedIssuer entry; signature, audience, and AllowedClaims checks against that entry are the security boundary.
func NewOIDCValidator ¶ added in v0.2.149
func NewOIDCValidator(issuers []TrustedIssuer) (*OIDCValidator, error)
NewOIDCValidator constructs an OIDCValidator for the given trusted issuers. SSRF-safe JWKS fetches are the default; set AllowPrivateIPJWKSHosts on an issuer to allow private-IP JWKS only for the listed hostnames, or AllowPrivateIPJWKS to disable SSRF protection entirely for that issuer.
func (*OIDCValidator) BearerTypHeaders ¶ added in v0.4.0
func (v *OIDCValidator) BearerTypHeaders(issuer string) []string
BearerTypHeaders returns the JWT typ header values accepted for Bearer tokens from the given issuer, defaulting to ["at+jwt"] (RFC 9068 §4) when the issuer is unknown or has no AcceptedTypHeaders configured.
func (*OIDCValidator) Validate ¶ added in v0.2.149
func (v *OIDCValidator) Validate(ctx context.Context, tokenString string, defaultAudiences []string) (*SubjectIdentity, error)
Validate verifies a JWT against the configured trusted issuers. The unverified iss claim routes to the matching TrustedIssuer entry; the signature, audience, and AllowedClaims checks that follow are the security boundary. defaultAudiences applies when the matched entry's AllowedAudiences is empty.
Returns ErrIssuerNotTrusted (wrapped) when the token cannot be routed to a configured issuer (not a JWT, missing iss, or iss not in the set).
type Option ¶ added in v0.2.121
type Option func(*Server)
Option configures a Server during construction.
func WithAuditor ¶ added in v0.2.121
WithAuditor sets the security auditor used for OAuth audit events. Passing nil panics; use security.NewAuditor(nil, false) for a noop.
func WithClientRegistrationRateLimiter ¶ added in v0.2.121
func WithClientRegistrationRateLimiter(rl *security.ClientRegistrationRateLimiter) Option
WithClientRegistrationRateLimiter sets the time-windowed rate limiter for /oauth/register. Prevents resource exhaustion via repeated registration / deletion cycles.
func WithDPoPNonceProvider ¶ added in v0.2.150
func WithDPoPNonceProvider(provider DPoPNonceProvider) Option
WithDPoPNonceProvider enables RFC 9449 §8 nonce enforcement. When set, ValidateDPoPProof requires every DPoP proof to carry a currently-valid server-issued nonce; proofs without one are rejected with ErrDPoPNonceInvalid. Pass NewHMACNonceProvider for a stateless HMAC-based implementation.
func WithDPoPReplayCache ¶ added in v0.2.150
func WithDPoPReplayCache(cache DPoPReplayCache) Option
WithDPoPReplayCache sets the DPoP proof replay cache. When set, the server uses this cache to detect replayed DPoP proofs across requests. When not set, each request creates a transient in-memory cache with no cross-request replay protection — use NewMemoryDPoPReplayCache() for single-process deployments, or a Valkey-backed implementation for multi-instance deployments.
func WithExchanger ¶ added in v0.3.0
WithExchanger enables the brokered RFC 8693 token-exchange flow. When a client sends an `audience` parameter with the token-exchange grant, the server validates the subject token, enforces the per-client audience allowlist (Config.TokenExchangeClientAudiences), and delegates the downstream exchange to e. The host owns the audience→downstream-issuer mapping; mcp-oauth owns validation, policy, and audit.
Without this option, requests carrying an audience parameter are rejected with invalid_target.
func WithInstrumentation ¶ added in v0.2.121
func WithInstrumentation(inst *instrumentation.Instrumentation) Option
WithInstrumentation installs an OpenTelemetry pipeline on the server. Build the *instrumentation.Instrumentation with instrumentation.New and pass it here. The same instance should be passed to the storage constructor (memory.WithInstrumentation / valkey.WithInstrumentation) so both the server and the store share one pipeline.
func WithMetadataFetchRateLimiter ¶ added in v0.2.121
func WithMetadataFetchRateLimiter(rl *security.RateLimiter) Option
WithMetadataFetchRateLimiter sets the per-domain rate limiter for Client ID Metadata Document fetches. Prevents abuse via repeated metadata fetches from many distinct URLs.
func WithRateLimiter ¶ added in v0.2.121
func WithRateLimiter(rl *security.RateLimiter) Option
WithRateLimiter sets the IP-based rate limiter consulted on every authenticated request.
func WithSecurityEventRateLimiter ¶ added in v0.2.121
func WithSecurityEventRateLimiter(rl *security.RateLimiter) Option
WithSecurityEventRateLimiter sets the rate limiter that bounds the emission of security-event log lines. Prevents log flooding from repeated failures (e.g. a malformed-token attack against /oauth/token).
func WithSessionCreationHandler ¶ added in v0.2.121
func WithSessionCreationHandler(handler SessionCreationHandler) Option
WithSessionCreationHandler registers a callback that fires synchronously when a new token family is created during authorization-code exchange. The handler is only invoked when the token store implements storage.RefreshTokenFamilyStore; a startup warning is logged if the configured store does not support families.
func WithSessionRevocationHandler ¶ added in v0.2.121
func WithSessionRevocationHandler(handler SessionRevocationHandler) Option
WithSessionRevocationHandler registers a callback that fires when a token family is revoked (e.g. on logout or reuse detection). Lets consumers clean up per-session state keyed on the family ID.
func WithSubjectTokenValidator ¶ added in v0.2.149
func WithSubjectTokenValidator(tokenType string, v SubjectTokenValidator) Option
WithSubjectTokenValidator registers a custom SubjectTokenValidator for the given subject_token_type URN. Use this to register a custom validator alongside or instead of OIDCValidator.
func WithTokenRefreshHandler ¶ added in v0.2.121
func WithTokenRefreshHandler(handler TokenRefreshHandler) Option
WithTokenRefreshHandler registers a callback that fires after a provider token is refreshed (proactively near expiry, or reactively when an expired token is encountered during validation). userID and familyID are populated only when the token store implements storage.TokenMetadataGetter; a startup warning is logged otherwise.
func WithTrustedAudiences ¶ added in v0.6.0
WithTrustedIssuers registers external JWT issuers whose tokens this server accepts. The same validator is consulted in two places:
- RFC 8693 token exchange: subject_token of type id_token, access_token, or jwt is routed to the matching issuer entry.
- ValidateToken: a Bearer JWT at /mcp is accepted when its iss matches a configured entry. Signature is verified via the entry's JWKS; aud is checked against AllowedAudiences (defaulting to the server's ResourceIdentifier when empty); the typ header is checked against AcceptedTypHeaders (default RFC 9068 typ=at+jwt).
WithTrustedAudiences sets additional OAuth client IDs whose tokens are accepted by ValidateToken, alongside the server's own ResourceIdentifier. This covers SSO token forwarding: an aggregator (e.g. muster) issues tokens with its own client_id as audience; adding that client_id here lets downstream servers accept forwarded tokens without a separate auth flow.
Equivalent to setting Config.TrustedAudiences; provided as a functional option for symmetry with WithTrustedIssuers. If both are used, the option takes precedence over the Config field.
Empty slice is a no-op.
func WithTrustedIssuers ¶ added in v0.2.149
func WithTrustedIssuers(issuers []TrustedIssuer) Option
Use TrustedIssuer.AllowedClaims to constrain accepted subjects per issuer. Empty list is a no-op.
func WithTrustedProxyCIDRs ¶ added in v0.2.150
WithTrustedProxyCIDRs registers the CIDRs from which X-Forwarded-Proto and X-Forwarded-Host headers are trusted for DPoP htu URL reconstruction. Required when the server runs behind agw, Envoy, or any reverse proxy that terminates TLS. Leave unset (or pass nil) when the server is directly exposed.
func WithUserRateLimiter ¶ added in v0.2.121
func WithUserRateLimiter(rl *security.RateLimiter) Option
WithUserRateLimiter sets the user-based rate limiter applied after authentication. Use alongside WithRateLimiter for layered protection.
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 RegisterClientResult ¶ added in v0.2.145
type RegisterClientResult struct {
Client *storage.Client
ClientSecret string // empty for public clients
RegistrationToken string // plaintext registration access token (RFC 7592)
}
RegisterClientResult carries both the client secret and the per-client registration access token issued at registration time (RFC 7591 §3.2.1 / RFC 7592 §2). The registration token is returned in plaintext exactly once and must be included in the DCR response by the handler.
type SelfIssuedExchangeRequest ¶ added in v1.0.1
type SelfIssuedExchangeRequest struct {
SubjectExchange
// DPoPJKT is the JWK thumbprint from a validated DPoP proof (RFC 9449 §6.1),
// written into the issued token's cnf.jkt claim. Empty when no proof was
// presented.
DPoPJKT string
// Options carries identity claims to emit; an explicit value takes precedence
// over the claims defaulted from the validated subject.
Options ExchangeOptions
}
SelfIssuedExchangeRequest is the input to SelfIssuedExchange, where this server signs the resulting token. DPoPJKT and Options apply only here because they shape a token this server issues; there is no Audience field, as self-issue sets aud via Resource (the RFC 8693 audience selects a broker target and is BrokeredExchange-only).
type Server ¶
type Server struct {
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, opts ...Option, ) (*Server, error)
New creates a new OAuth server. Functional options configure optional dependencies (encryptor, auditor, rate limiters, lifecycle handlers, instrumentation) and are applied after the server has been wired with its stores and the access-token issuer; option callbacks that propagate state to the storage backend can rely on the store being attached.
func NewWithCombined ¶ added in v0.2.108
func NewWithCombined( provider providers.Provider, store storage.Combined, config *Config, logger *slog.Logger, opts ...Option, ) (*Server, error)
NewWithCombined is an additive constructor for backends that implement the storage.Combined interface (both memory and valkey do). It avoids the awkward call site where the same *Store value is passed three times to New because TokenStore/ClientStore/FlowStore are separate parameters:
srv, err := server.NewWithCombined(provider, store, cfg, logger,
server.WithEncryptor(enc),
server.WithAuditor(aud),
)
New is unchanged so callers that split persistence across different backing stores (e.g. Postgres tokens + memory flows) keep working.
func (*Server) AcceptForwardedIDToken ¶ added in v0.2.102
func (s *Server) AcceptForwardedIDToken(ctx context.Context, bearerToken string) (*ForwardedIDTokenAcceptance, error)
AcceptForwardedIDToken validates a JWT forwarded from a trusted upstream identity provider and returns its verified claims plus a deterministic session identifier. It is the companion of the ValidateToken fast-path (server/flows.go:265) for the direct "accept this forwarded token" use case exposed to aggregators and bridges (e.g., Bedrock AgentCore → muster).
No `nonce` enforcement: this server never issued a nonce for forwarded tokens, so there is no expected value to bind against. Replay defence is the upstream's responsibility.
Preconditions (documented here because operators configuring a new bridge look at this godoc first):
- The configured provider implements providers.JWKSProvider. GitHub's provider does not qualify (it is OAuth 2.0 only). Validation returns a "no JWKS" error when invoked against an OAuth-only provider.
- Config.TrustedAudiences contains the audience the upstream IdP issued the token for.
- The provider's IssuerURL() matches the JWT's `iss` claim. The signature check requires this anyway.
Scope (deliberate — pure function, no side effects):
Does NOT fire SessionCreationHandler. That handler is gated on the RefreshTokenFamilyStore interface and the authorization-code family lifecycle, neither of which applies to forwarded tokens. Callers that want a first-seen hook for a given SessionID should build that on top using their own seen-set keyed on acceptance.SessionID with TTL bounded by acceptance.ExpiresAt.
Does NOT mirror the token into TokenStore. TokenStore is keyed by userID and is owned by the authorization-code flow. Aggregators that need to retrieve the forwarded token later (e.g., to attach it to downstream MCP calls) must store it in a structure they own, keyed however they like.
Idempotency: the function is pure. Repeat calls with the same token return the same SessionID and emit the same audit event. Any "first call vs repeat" semantics live with the caller.
Token expiry: the library does not refresh forwarded tokens. When the JWT expires, this function returns an error and the caller must propagate 401.
Returns ErrTrustedAudienceMismatch when the token's `aud` does not match any entry in Config.TrustedAudiences. Other errors (signature invalid, issuer mismatch, JWT expired, JWT not yet valid, provider has no JWKS, parse error) are returned wrapped.
func (*Server) AcceptTrustedIssuerToken ¶ added in v0.8.0
func (s *Server) AcceptTrustedIssuerToken(ctx context.Context, bearerToken string) (*ForwardedIDTokenAcceptance, error)
AcceptTrustedIssuerToken validates a Bearer JWT against the server's WithTrustedIssuers configuration and returns a ForwardedIDTokenAcceptance identical in shape to [AcceptForwardedIDToken]. This lets aggregators treat a raw TrustedIssuers-validated token (e.g. a Kubernetes ServiceAccount projected token) as a forwarded credential — the same ext-<hex> session-ID derivation applies, so cross-hop audit-log correlation is preserved.
Intended call pattern: an aggregator first calls [AcceptForwardedIDToken]; when that returns ErrTrustedAudienceMismatch (the token's aud is the server's own resource identifier, not a TrustedAudiences entry), it falls back here.
Preconditions:
- WithTrustedIssuers must be configured; otherwise ErrIssuerNotTrusted is returned and the caller should fall through.
- The token's iss must match a configured TrustedIssuer entry.
- Audience and AllowedClaims checks for that entry pass.
- The typ header matches TrustedIssuer.AcceptedTypHeaders (empty string "" is needed for Kubernetes ServiceAccount tokens, which carry no typ header).
Returns ErrIssuerNotTrusted when no TrustedIssuers validator is configured or the token's iss is not recognised. Other errors (signature invalid, audience rejected, AllowedClaims mismatch, typ mismatch, JWT expired) are returned wrapped.
func (*Server) BrokeredExchange ¶ added in v1.0.1
func (s *Server) BrokeredExchange(ctx context.Context, req BrokeredExchangeRequest) (*TokenExchangeResult, error)
BrokeredExchange implements the RFC 8693 flow where the resulting token is issued by a downstream issuer reached through the configured Exchanger. The subject token (and, when present, the actor token for the §4.4 delegation chain) are validated against the server's trusted issuers before the Exchanger is invoked; the verified actor identity is forwarded to it.
Policy: an Exchanger must be configured (ErrInvalidTarget otherwise) and the audience must be in the client's Config.TokenExchangeClientAudiences allowlist. Self-delegation (actor equal to subject) is dropped to a no-op, and no refresh token is issued; the result's expiry is the downstream token's and clients re-exchange. Every outcome is audited with the client ID, subject, requested audience, granted scope, and the deterministic cross-hop session ID derived from the subject token, so broker audit lines correlate with downstream MCP audit lines for the same token.
func (*Server) CanRegisterWithTrustedRedirectURI ¶ added in v0.2.125
func (s *Server) CanRegisterWithTrustedRedirectURI(redirectURIs []string) (allowed bool, matchedURI string, err error)
CanRegisterWithTrustedRedirectURI reports whether a registration request can proceed without a RegistrationAccessToken because every redirect URI in the request matches an entry in TrustedPublicRegistrationRedirectURIs after RFC 3986 normalization. Strict matching is the only mode: a permissive variant (matching any URI when at least one matches) would allow an attacker to attach arbitrary callbacks alongside a trusted one. A single non-matching URI causes the request to fall through to the token gate.
Returns the first matched (canonical) URI for audit logging.
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) DPoPNonceProvider ¶ added in v0.2.150
func (s *Server) DPoPNonceProvider() DPoPNonceProvider
DPoPNonceProvider returns the configured nonce provider, or nil when RFC 9449 §8 nonce enforcement is not enabled.
func (*Server) DPoPReplayCache ¶ added in v0.2.150
func (s *Server) DPoPReplayCache() DPoPReplayCache
DPoPReplayCache returns the configured DPoP replay cache, or nil if none was set.
func (*Server) DeleteClient ¶ added in v0.2.145
DeleteClient removes a client by ID (RFC 7592 §2.4).
func (*Server) EnsureConfidentialClient ¶ added in v0.11.0
func (s *Server) EnsureConfidentialClient(ctx context.Context, clientID, clientSecret string, scopes []string) (seeded bool, err error)
EnsureConfidentialClient idempotently seeds a confidential OAuth client with a caller-supplied id and secret. Unlike RegisterClientV2 (which generates a random id and secret), this is used to declaratively provision a known confidential client -- e.g. a token-exchange broker -- from a secret mounted at startup, so that a wiped client store self-heals.
It is idempotent: if a client with the given id already exists and its stored secret hash matches the supplied secret, it is left untouched. Otherwise the client record is (re)written with a fresh bcrypt hash of the supplied secret. Returns whether the store was written (true) or already up to date (false).
func (*Server) ExchangeAuthorizationCode ¶
func (s *Server) ExchangeAuthorizationCode(ctx context.Context, code, clientID, redirectURI, resource, codeVerifier, dpopJKT string) (*oauth2.Token, string, error)
ExchangeAuthorizationCode exchanges an authorization code for tokens. dpopJKT is the JWK thumbprint from the DPoP proof header; empty string for bearer-only clients. resource is optional per RFC 8707.
func (*Server) Exchanger ¶ added in v0.3.0
Exchanger returns the configured Exchanger, or nil when brokered token exchange is disabled.
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) IntrospectToken ¶ added in v0.2.131
func (s *Server) IntrospectToken(ctx context.Context, accessToken, requestingClient string) map[string]any
IntrospectToken returns the RFC 7662 introspection payload for accessToken, or {"active": false} when requestingClient is not authorized to learn it. Denied probes return no other fields so the endpoint is not an oracle for token or user enumeration.
Callers MUST authenticate requestingClient before invoking this method. The cross-client gate trusts that identifier as-is; passing an attacker- supplied or unauthenticated value defeats the gate entirely.
func (*Server) LogValue ¶ added in v0.2.123
LogValue implements slog.LogValuer so callers can emit a structured snapshot of the server's security posture:
logger.Info("oauth server initialized", "server", srv)
Fields are limited to state that is either derived (instrumentation-wired) or mutated by [applySecureDefaults] (production mode, redirect-URI validation flags) — i.e. state the caller's pre-build Config does not faithfully reflect. Encryption status is reported by the store's own LogValue; the server no longer holds the encryptor.
func (*Server) PublicJWKS ¶ added in v0.2.120
func (s *Server) PublicJWKS() (*jose.JSONWebKeySet, error)
PublicJWKS returns the JWKS envelope to be served at the JWKS discovery endpoint. In AccessTokenFormatJWT mode the returned set contains the public half of the access-token signing key; in opaque mode the set is empty. Callers serving the HTTP endpoint should respond 404 in the empty case rather than serving an empty key array.
Exported so the http.Handler in the root oauth package can build the response without reaching into server-package internals.
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) RefreshSession ¶ added in v0.2.122
RefreshSession refreshes the upstream provider token for the session identified by familyID. Equivalent to the public /oauth/token refresh-token grant, but callable in-process from any goroutine — useful for integrations that need to force a refresh from a request thread (e.g. when a cached ID token has expired and the caller is about to forward it).
Returns the new provider token. The caller can extract the id_token from the Extra field via ExtractIDToken for SSO-forwarding flows.
Overlapping in-process calls for the same familyID are coalesced — only one refresh hits the upstream provider; the rest wait for and share the result. Calls that arrive sequentially (after the previous one returns) each run their own refresh, as expected. This matches the proactive-refresh path's coalescing behavior.
Returns an error if:
- the storage backend does not implement storage.ActiveRefreshTokenByFamilyStore
- no entry for the family exists at all (wrapped storage.ErrRefreshTokenFamilyNotFound)
- the family exists but every member is revoked (wrapped storage.ErrRefreshTokenFamilyRevoked)
- the upstream provider's refresh call fails
The same lifecycle hooks fire as for the public refresh-token-grant path: TokenRefreshHandler is called with the userID + familyID + new token, refresh-token rotation produces a new family generation, and the new tokens are saved to the TokenStore.
Failure semantics: if the call fails after the active refresh token has been atomically deleted (e.g. the upstream provider's refresh errors out), the family is left without a usable refresh token. The caller should treat that as session-loss and propagate a 401 / force re-authentication. This matches RefreshAccessToken behavior — it is the standard OAuth 2.1 rotation contract, not a regression from this API.
Cross-process race: when the storage backend is shared across instances and another instance rotates the family between this call's lookup and its atomic delete, the atomic delete returns "not found" and is surfaced to the caller as an invalid-grant error. RefreshSession does not retry across this race because the underlying error is indistinguishable from a genuine reuse-detection event, and a naive retry would falsely trigger family revocation on the rotated state. Callers in distributed deployments that observe this should re-read the cached entry — another instance has produced a fresh token.
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) RegisterClientV2 ¶ added in v0.2.145
func (s *Server) RegisterClientV2(ctx context.Context, clientName, clientType, tokenEndpointAuthMethod string, redirectURIs []string, scopes []string, clientIP string, maxClientsPerIP int) (*RegisterClientResult, error)
RegisterClientV2 is the registration path that carries the registration access token back to the handler. It replaces RegisterClient once all call sites are migrated.
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).
Three input shapes are accepted:
- Self-issued JWT access token: jti is added to RevokedTokenStore so the next ValidateToken call rejects it. The denylist entry auto-expires at the JWT's own exp.
- Opaque access token: TokenStore deletion.
- Refresh token: TokenStore deletion + family revocation.
Per RFC 7009 §2.2 revocation always returns success when the token is not found or already invalid — clients have no way to distinguish "this token was never issued" from "this token has been forgotten" from "revocation succeeded", and surfacing those distinctions enables token scanning attacks.
func (*Server) SaveClient ¶ added in v0.2.145
SaveClient persists an updated client record.
func (*Server) SelfIssuedExchange ¶ added in v1.0.1
func (s *Server) SelfIssuedExchange(ctx context.Context, req SelfIssuedExchangeRequest) (*TokenExchangeResult, error)
SelfIssuedExchange validates the subject token (and optional actor token) against the registered SubjectTokenValidator, then issues a JWT access token this server signs. req.Resource becomes the aud claim, defaulting to the server's own resource identifier when empty. req.Scope is intersected against the per-issuer AllowedScopes envelope from TrustedIssuer configuration. req.DPoPJKT, when non-empty, is written into the cnf.jkt claim (RFC 9449 §6.1).
When an actor token is present it is validated against the server's trusted issuers and written as the act claim; any act chain already on the subject token is nested beneath it so a multi-hop delegation chain is preserved (RFC 8693 §4.4). A chain deeper than the bound is rejected.
req.Options identity fields are emitted as standard JWT claims; email, email_verified, name and groups default from the validated subject when Options leaves them unset, and an explicit Options value takes precedence.
func (*Server) SessionIDForBearer ¶ added in v0.18.0
deriveForwardedSessionID produces the deterministic "ext-<hex>" session identifier. See the SessionID field godoc and docs/security.md for the correlation property and the SessionIDHMACKey operator caveat.
The input is domain-separated so Config.SessionIDHMACKey can be safely reused for other keyed derivations without risking cross-purpose collisions. hash.Hash.Write never returns an error — the doc on hash.Hash guarantees it — so the results are ignored. SessionIDForBearer returns the stable session identifier for a validated bearer that has no refresh-token family: forwarded ID tokens, trusted-issuer tokens, and self-issued exchange JWTs. It is the same derivation the forwarded-token and token-exchange paths use, exported so the resource-server middleware can assign a session to every validated token, not only those backed by stored family metadata.
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 string, redirectURI *url.URL, scope, resource, codeChallenge, codeChallengeMethod, clientState string, authOpts *providers.AuthorizationURLOptions) (string, error)
StartAuthorizationFlow starts a new OAuth authorization flow. redirectURI must be a canonical, registered URI obtained from [ValidateRedirectURIForAuthorization]. This function re-runs the registered-URI check as defense-in-depth but the canonical value is what is stored in the authorization state and used as the redirect target. 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) SubjectValidatorFor ¶ added in v0.2.149
func (s *Server) SubjectValidatorFor(tokenType string) SubjectTokenValidator
SubjectValidatorFor returns the SubjectTokenValidator registered for the given subject_token_type URN, or nil if none is registered.
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) TrustedProxyCIDRs ¶ added in v0.2.150
TrustedProxyCIDRs returns the list of CIDRs trusted for DPoP htu reconstruction.
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) ValidateRedirectURIForAuthorization ¶ added in v0.2.130
func (s *Server) ValidateRedirectURIForAuthorization(ctx context.Context, clientID, redirectURI string) (*url.URL, error)
ValidateRedirectURIForAuthorization resolves the redirect URI to its canonical, registered form for the client. Callers redirecting /authorize protocol errors back to the client MUST use this return value as the redirect target — it provably originates from server-side storage, not from the request, satisfying RFC 6749 §3.1.2.4.
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 across every accepted bearer format. Validation is format-agnostic by design — operators of one server instance pick one issuance format, but operators running multiple instances or upgrading progressively can rely on the validator accepting every format their consumers have already received.
Validation pipeline:
- Self-issued JWT (when AccessTokenFormatJWT mode is on AND the bearer is a JWT whose iss claim equals Config.Issuer). Local signature verification against the configured public key. No provider round-trip. See [Server.validateSelfIssuedJWT] for the security boundary checks (signature, typ, exp, aud, jti, family).
- Trusted-issuer JWT (when WithTrustedIssuers is configured AND the bearer's iss matches a configured entry). Signature verified via the entry's JWKS; aud checked against the entry's AllowedAudiences (defaulting to Config.GetResourceIdentifier when empty); typ header checked against the entry's AcceptedTypHeaders (default RFC 9068 §4 at+jwt). A non-matching iss returns ErrIssuerNotTrusted and falls through to subsequent branches; a typ failure on a token whose aud is in Config.TrustedAudiences falls through to the forwarded-ID-token branch (it is an ID token, not an access token); any other validation failure is a hard rejection.
- Forwarded ID token (when the bearer is a JWT whose aud matches Config.TrustedAudiences). Verified via the upstream provider's JWKS for SSO token forwarding.
- Opaque token (TokenStore lookup, then provider userinfo). The catch-all for non-JWT bearers.
Steps 1, 2, and 3 are opt-in (AccessTokenFormatJWT, WithTrustedIssuers, TrustedAudiences respectively); step 4 is always available. Rate limiting should be done at the HTTP layer with IP address, not here with the token.
Error responses: callers SHOULD respond with a single 401 form regardless of the returned error class. The error message distinguishes expired / revoked / audience-mismatch / family-revoked / unknown for audit logs and operator dashboards; surfacing those distinctions to the client enables token-state probing across all three branches (opaque, SSO, self-issued JWT). Use the returned error for logging, not for setting the response body.
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 SubjectExchange ¶ added in v1.0.1
type SubjectExchange struct {
// Subject is the RFC 8693 subject_token being exchanged.
Subject TypedToken
// Actor is the RFC 8693 §4.4 actor_token. Its zero value means no delegation.
Actor TypedToken
// Resource is the RFC 8707 target URI. On SelfIssuedExchange it becomes the
// issued token's aud, defaulting to the server's resource identifier when
// empty. On BrokeredExchange it is forwarded to the host Exchanger.
Resource string
Scope string
}
SubjectExchange carries the RFC 8693 inputs common to both exchange methods: the subject token (and optional actor token) and the requested target.
type SubjectIdentity ¶ added in v0.2.149
type SubjectIdentity struct {
Subject string
Issuer string
AllowedScopes []string
Claims *oidc.IDTokenClaims
// ConfirmationJKT is the RFC 9449 §6.1 cnf.jkt thumbprint the token is bound
// to, empty when the token carries no proof-of-possession confirmation.
ConfirmationJKT string
}
SubjectIdentity carries the verified identity extracted from a JWT. Claims is the full verified payload; callers that only need the canonical fields can use Subject, Issuer, and AllowedScopes directly.
type SubjectTokenValidator ¶ added in v0.2.149
type SubjectTokenValidator interface {
Validate(ctx context.Context, tokenString string, defaultAudiences []string) (*SubjectIdentity, error)
}
SubjectTokenValidator verifies a JWT against a set of trusted issuers and returns the verified identity. defaultAudiences applies when the matched issuer's AllowedAudiences is empty; pass nil to accept any audience.
type TokenExchangeResult ¶ added in v0.2.150
type TokenExchangeResult struct {
AccessToken string
ExpiresAt time.Time
Scope string
IssuedTokenType string
}
TokenExchangeResult holds the output of a successful token exchange.
type TokenExchangeUnsupportedTypeError ¶ added in v0.2.150
type TokenExchangeUnsupportedTypeError struct {
// contains filtered or unexported fields
}
TokenExchangeUnsupportedTypeError is returned when no validator is registered for the requested token type. Role is "subject" or "actor".
func (*TokenExchangeUnsupportedTypeError) Error ¶ added in v0.2.150
func (e *TokenExchangeUnsupportedTypeError) Error() string
func (*TokenExchangeUnsupportedTypeError) Role ¶ added in v0.5.0
func (e *TokenExchangeUnsupportedTypeError) Role() string
Role returns "subject" or "actor" identifying which token in the request was of an unsupported type.
func (*TokenExchangeUnsupportedTypeError) TokenType ¶ added in v0.2.150
func (e *TokenExchangeUnsupportedTypeError) TokenType() string
TokenType returns the unrecognised token-type URN value.
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)
type TrustedIssuer ¶ added in v0.2.149
type TrustedIssuer struct {
// Issuer is the expected iss claim value. Only tokens whose iss equals this
// value will be routed to this entry.
Issuer string
// JwksURL is the JWKS endpoint used to fetch the public key set.
JwksURL string
// AllowedAudiences is the list of accepted aud values. An empty list accepts
// any audience (not recommended for production).
AllowedAudiences []string
// AllowedScopes caps the scopes that can be issued for tokens from this issuer.
// Nil means no per-issuer restriction.
AllowedScopes []string
// AllowedClaims constrains which tokens are accepted by requiring each named
// claim to match its pattern. Keys are JWT claim names; values are exact
// strings or glob patterns where '*' matches any sequence of characters
// (including '/') and '?' matches any single character. Use '*' freely
// across path segments, e.g. "system:serviceaccount:ns:*" or
// "repo:org/repo:*". Absent claims and claims with non-string values
// (numbers, arrays, objects) are rejected. Nil or empty means no claim
// restrictions.
AllowedClaims map[string]string
// SubjectClaim names the verified claim whose value becomes
// SubjectIdentity.Subject (and thus the sub of any token issued from this
// identity). Empty keeps the standard sub claim. Use this when the issuer's sub is an opaque identifier
// but a different claim (e.g. "email") carries the canonical subject the
// downstream relies on. Fail-closed: if set and the claim is absent or not a
// non-empty string, the token is rejected.
SubjectClaim string
// AllowPrivateIPJWKS allows JwksURL to resolve to a private or loopback IP
// address. Set this when the JWKS endpoint is an in-cluster service (e.g.
// the Kubernetes API server's /openid/v1/jwks) that is not reachable via a
// public address.
//
// WARNING: Disables SSRF protection for this issuer's JWKS fetch. Only set
// when JwksURL is a known, controlled in-cluster endpoint. Prefer
// AllowPrivateIPJWKSHosts when the endpoint is a specific known service.
AllowPrivateIPJWKS bool
// AllowPrivateIPJWKSHosts lists the specific hostnames whose JWKS URL is
// permitted to resolve to a private IP. All other hosts retain SSRF
// protection. Use this instead of AllowPrivateIPJWKS when the private
// endpoint is a known in-cluster service (e.g.
// muster.agentic-platform.svc.cluster.local). Ignored when
// AllowPrivateIPJWKS is true.
AllowPrivateIPJWKSHosts []string
// RootCAs is the CA pool used to verify this issuer's JWKS endpoint TLS
// certificate when AllowPrivateIPJWKS or AllowPrivateIPJWKSHosts is set and
// the endpoint presents a certificate from an internal CA. nil uses the
// system pool.
RootCAs *x509.CertPool
// AcceptedTypHeaders lists the JWT typ header values accepted when a
// Bearer token from this issuer is presented to the resource server.
// Empty defaults to ["at+jwt"] (RFC 9068 §4). Issuers that issue plain
// JWTs need an explicit list: Kubernetes ServiceAccount tokens carry no
// typ header at all, so use [""] to accept them. Signature, audience,
// and claim checks still apply unchanged.
AcceptedTypHeaders []string
}
TrustedIssuer configures a trusted external token issuer for OIDCValidator. The JwksURL and Issuer are intentionally independent: the JWKS location is not derived from Issuer via OIDC discovery, so callers can point at an in-cluster proxy without changing the Issuer value.
type TypedToken ¶ added in v1.0.1
TypedToken is an RFC 8693 token paired with its token-type URN.
Source Files
¶
- access_token.go
- authcode.go
- cimd.go
- cimd_cache.go
- client.go
- config.go
- config_interstitial.go
- config_trusted_redirect_uris.go
- config_validation.go
- config_validation_cors.go
- config_validation_security.go
- doc.go
- dpop.go
- dpop_nonce.go
- dpop_replay.go
- flows.go
- forwarded.go
- introspect.go
- jwt.go
- logvalue.go
- options.go
- redirect_uri_security.go
- refresh.go
- refresh_session.go
- revoke.go
- scope.go
- server.go
- sso.go
- subject_validator.go
- token_exchange.go
- token_exchange_broker.go
- validate.go
- validation.go