Documentation
¶
Overview ¶
Package oauth provides small, composable OAuth 2.0/2.1 primitives used by AppTheory components (e.g. Remote MCP protected resources) and by Autheory (an OAuth Authorization Server built on AppTheory).
The package intentionally focuses on wire-level helpers: - RFC9728 protected resource discovery (WWW-Authenticate + metadata) - RFC8414 authorization server metadata helpers - RFC7591 Dynamic Client Registration request/response types + validation - PKCE utilities
Index ¶
- Constants
- Variables
- func AuthorizationServerMetadataHandler(md *AuthorizationServerMetadata) apptheory.Handler
- func BearerTokenFromHeaders(headers map[string][]string) (string, error)
- func BootstrapOIDCDiscovery(ctx context.Context, discoveryURL string, issuer string, client *http.Client) (jwksURI string, err error)
- func CanonicalResourceURL(raw string) string
- func CanonicalizeIssuerURL(raw string) (string, bool)
- func InsufficientScopeWWWAuthenticate(scopes []string, resourceMetadataURL string) string
- func NewMCPProtectedResourceDiscoveryHandler(config MCPServerConfig) (apptheory.Handler, error)
- func NewOpaqueToken() (string, error)
- func NewPKCECodeVerifier() (string, error)
- func NormalizeRequestOrigin(raw string) (string, bool)
- func PKCEChallengeS256(verifier string) (string, error)
- func PKCEVerifyS256(verifier, expectedChallenge string) (bool, error)
- func ProtectedResourceMetadataHandler(md *ProtectedResourceMetadata) apptheory.Handler
- func ProtectedResourceMetadataURLForRequest(headers map[string][]string) (string, bool)
- func ProtectedResourceWWWAuthenticate(resourceMetadataURL string) string
- func RFC9728ResourceMetadataURL(resourceURL string) (string, bool)
- func RegisterMCPServer(app *apptheory.SecureApp, handler apptheory.Handler, config MCPServerConfig) error
- func RequireBearerTokenMiddleware(opts RequireBearerTokenOptions) apptheory.Middleware
- func ResourceMetadataURLFromMcpEndpoint(mcpEndpoint string) (string, bool)
- func ValidateDynamicClientRegistrationRequest(req *DynamicClientRegistrationRequest, policy DynamicClientRegistrationPolicy) error
- func ValidatePKCECodeVerifier(verifier string) error
- type AutheoryProvider
- func (p *AutheoryProvider) AuthorizationURL(req AuthorizationRequest) (string, error)
- func (p *AutheoryProvider) Discovery(ctx context.Context) (*ProviderMetadata, error)
- func (p *AutheoryProvider) ExchangeCode(ctx context.Context, req CodeExchangeRequest) (*TokenSet, error)
- func (p *AutheoryProvider) Refresh(ctx context.Context, req RefreshRequest) (*TokenSet, error)
- func (p *AutheoryProvider) ValidateAccessToken(ctx context.Context, token string, expected TokenExpectations) (*TokenClaims, error)
- type AutheoryProviderOptions
- type AuthorizationCodeRecord
- type AuthorizationCodeStore
- type AuthorizationRequest
- type AuthorizationServerMetadata
- type AuthorizationServerMetadataOption
- type BearerTokenClaims
- type BearerTokenClaimsValidator
- type BearerTokenRecord
- type BearerTokenValidationOptions
- type BearerTokenValidator
- type CodeExchangeRequest
- type DynamicClientRegistrationPolicy
- type DynamicClientRegistrationRequest
- type DynamicClientRegistrationResponse
- type JWKSValidatorOptions
- type MCPServerConfig
- type MemoryAuthorizationCodeStore
- type MemoryRefreshTokenStore
- func (s *MemoryRefreshTokenStore) Consume(_ context.Context, token string) (*RefreshTokenRecord, error)
- func (s *MemoryRefreshTokenStore) Delete(_ context.Context, token string) error
- func (s *MemoryRefreshTokenStore) Get(_ context.Context, token string) (*RefreshTokenRecord, error)
- func (s *MemoryRefreshTokenStore) Put(_ context.Context, rec *RefreshTokenRecord) error
- type ProtectedResourceMetadata
- type Provider
- type ProviderError
- type ProviderErrorClass
- type ProviderMetadata
- type RefreshRequest
- type RefreshTokenRecord
- type RefreshTokenStore
- type RequireBearerTokenOptions
- type TokenClaims
- type TokenExpectations
- type TokenSet
Constants ¶
const ( // MCPPath is the conventional AppTheory MCP endpoint path. MCPPath = "/mcp" // OAuthProtectedResourcePath is the generic RFC 9728 protected-resource // metadata path. OAuthProtectedResourcePath = "/.well-known/oauth-protected-resource" // OAuthProtectedResourceMCPPath is the RFC 9728 path-scoped metadata path // for the conventional MCP endpoint. OAuthProtectedResourceMCPPath = "/.well-known/oauth-protected-resource/mcp" // OAuthAuthorizationServerMCPPath is the RFC 8414 path-scoped metadata path // for an MCP authorization server. OAuthAuthorizationServerMCPPath = "/.well-known/oauth-authorization-server/mcp" )
const ContextKeyBearerClaims = "oauth.bearer_claims" //nolint:gosec // context key, not a credential
ContextKeyBearerClaims is set on apptheory.Context when a claims validator is configured.
const ContextKeyBearerToken = "oauth.bearer_token" //nolint:gosec // context key, not a credential
ContextKeyBearerToken is set on apptheory.Context by RequireBearerTokenMiddleware.
Variables ¶
var ( // ErrMissingBearerToken is returned when an Authorization header is missing. ErrMissingBearerToken = errors.New("missing bearer token") // ErrInvalidAuthorizationHeader is returned when the Authorization header cannot be parsed as Bearer. ErrInvalidAuthorizationHeader = errors.New("invalid authorization header") // ErrInvalidBearerToken indicates an unknown or otherwise invalid bearer token. ErrInvalidBearerToken = errors.New("invalid bearer token") // ErrBearerTokenExpired indicates an expired bearer token. ErrBearerTokenExpired = errors.New("bearer token expired") // ErrBearerTokenInvalidAudience indicates a bearer token is not bound to the required resource. ErrBearerTokenInvalidAudience = errors.New("bearer token invalid audience") // ErrBearerTokenInsufficientScope indicates a bearer token lacks a required scope. ErrBearerTokenInsufficientScope = errors.New("bearer token insufficient scope") // ErrInvalidURL is returned when a required URL is not a valid absolute URL. ErrInvalidURL = errors.New("invalid url") // ErrAuthorizationCodeNotFound indicates an unknown/consumed authorization code. ErrAuthorizationCodeNotFound = errors.New("authorization code not found") // ErrAuthorizationCodeExpired indicates an expired authorization code. ErrAuthorizationCodeExpired = errors.New("authorization code expired") // ErrRefreshTokenNotFound indicates an unknown/revoked refresh token. ErrRefreshTokenNotFound = errors.New("refresh token not found") // ErrRefreshTokenExpired indicates an expired refresh token. ErrRefreshTokenExpired = errors.New("refresh token expired") )
Functions ¶
func AuthorizationServerMetadataHandler ¶
func AuthorizationServerMetadataHandler(md *AuthorizationServerMetadata) apptheory.Handler
AuthorizationServerMetadataHandler returns an AppTheory handler that serves the RFC8414 authorization server metadata document.
func BearerTokenFromHeaders ¶
BearerTokenFromHeaders extracts the bearer token from normalized headers.
func BootstrapOIDCDiscovery ¶ added in v6.1.0
func BootstrapOIDCDiscovery(ctx context.Context, discoveryURL string, issuer string, client *http.Client) (jwksURI string, err error)
BootstrapOIDCDiscovery fetches an OIDC discovery document, verifies that the document's issuer equals the configured issuer, and returns the document's jwks_uri. The discovery URL and the returned key-set URL must both be absolute HTTPS URLs; a document that omits jwks_uri, an issuer mismatch, and any redirect all fail closed.
A non-nil client is caller-owned and keeps its own connection policy, so a caller can point the bootstrap at a loopback server for local development; when it is nil the framework installs its own transport, which refuses any endpoint host that is not a public address.
func CanonicalResourceURL ¶
CanonicalResourceURL trims whitespace and a trailing slash.
func CanonicalizeIssuerURL ¶
CanonicalizeIssuerURL trims trailing slashes from an issuer/base URL.
func InsufficientScopeWWWAuthenticate ¶ added in v6.1.0
InsufficientScopeWWWAuthenticate builds the RFC 6750 (RFC 9728-extended) insufficient_scope challenge carrying the route-derived remediation scope set.
func NewMCPProtectedResourceDiscoveryHandler ¶
func NewMCPProtectedResourceDiscoveryHandler(config MCPServerConfig) (apptheory.Handler, error)
NewMCPProtectedResourceDiscoveryHandler creates the request-time RFC 9728 discovery handler used by namespace MCP applications. The protected resource URL is reconstructed from AppTheory's canonical request headers; no resource origin is accepted in config.
func NewOpaqueToken ¶
NewOpaqueToken generates a URL-safe opaque token suitable for authorization codes and refresh tokens.
func NewPKCECodeVerifier ¶
NewPKCECodeVerifier generates a random PKCE code verifier (length ~43).
func NormalizeRequestOrigin ¶
NormalizeRequestOrigin validates and canonicalizes a request-derived origin. It accepts HTTPS, plus HTTP only for loopback hosts, and removes scheme- default ports.
func PKCEChallengeS256 ¶
PKCEChallengeS256 computes the S256 code challenge for a verifier.
func PKCEVerifyS256 ¶
PKCEVerifyS256 verifies a verifier matches an expected S256 challenge.
func ProtectedResourceMetadataHandler ¶
func ProtectedResourceMetadataHandler(md *ProtectedResourceMetadata) apptheory.Handler
ProtectedResourceMetadataHandler returns an AppTheory handler that serves the RFC9728 protected resource metadata document.
func ProtectedResourceMetadataURLForRequest ¶
ProtectedResourceMetadataURLForRequest derives an absolute root `/.well-known/oauth-protected-resource` URL from common proxy headers.
Prefer using ResourceMetadataURLFromMcpEndpoint with an explicit MCP endpoint URL for AWS Remote MCP deployments. This helper is intentionally root-only and does not attempt to infer path-scoped protected resources from request paths.
func ProtectedResourceWWWAuthenticate ¶
ProtectedResourceWWWAuthenticate builds the RFC9728 MCP-style discovery challenge.
Example:
Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"
func RFC9728ResourceMetadataURL ¶
RFC9728ResourceMetadataURL derives the protected resource metadata URL from any absolute protected resource identifier URL per RFC 9728 section 3.
For example:
https://api.example.com/mcp -> https://api.example.com/.well-known/oauth-protected-resource/mcp https://api.example.com/mcp/Arch -> https://api.example.com/.well-known/oauth-protected-resource/mcp/Arch
func RegisterMCPServer ¶
func RegisterMCPServer(app *apptheory.SecureApp, handler apptheory.Handler, config MCPServerConfig) error
RegisterMCPServer registers the conventional authenticated MCP POST route and both public RFC 9728 discovery routes on a SecureApp. The fixed posture split is deliberate: clients must fetch discovery before they have a token, while the MCP endpoint uses SecureApp's authenticated posture.
func RequireBearerTokenMiddleware ¶
func RequireBearerTokenMiddleware(opts RequireBearerTokenOptions) apptheory.Middleware
RequireBearerTokenMiddleware enforces `Authorization: Bearer <token>` and, when missing/invalid, returns 401 with the MCP 2025-06-18 compatible WWW-Authenticate challenge that points to protected resource metadata.
A ResourceMetadataURLTemplate is compiled here, at registration, and a malformed template panics: the middleware never serves a request whose challenge would advertise a broken or partially resolved discovery URL.
func ResourceMetadataURLFromMcpEndpoint ¶
ResourceMetadataURLFromMcpEndpoint derives the protected resource metadata URL from an MCP endpoint URL.
This compatibility alias intentionally performs no `/mcp` suffix validation; any absolute protected resource URL is accepted and transformed per RFC 9728.
func ValidateDynamicClientRegistrationRequest ¶
func ValidateDynamicClientRegistrationRequest(req *DynamicClientRegistrationRequest, policy DynamicClientRegistrationPolicy) error
ValidateDynamicClientRegistrationRequest validates an RFC7591 request against a policy.
func ValidatePKCECodeVerifier ¶
ValidatePKCECodeVerifier validates a verifier against RFC7636 constraints.
Types ¶
type AutheoryProvider ¶ added in v6.1.0
type AutheoryProvider struct {
// contains filtered or unexported fields
}
AutheoryProvider is the Autheory implementation of Provider.
func NewAutheoryProvider ¶ added in v6.1.0
func NewAutheoryProvider(opts AutheoryProviderOptions) (*AutheoryProvider, error)
NewAutheoryProvider returns a fail-closed Autheory provider adapter. The issuer must be an absolute URL; Discovery refuses a non-https discovery URL, a document issuer that does not match, and any redirect.
func (*AutheoryProvider) AuthorizationURL ¶ added in v6.1.0
func (p *AutheoryProvider) AuthorizationURL(req AuthorizationRequest) (string, error)
AuthorizationURL builds the upstream authorize URL. The adapter always sends resource (Autheory requires it, RFC 8707); a hint whose name collides with a required or reserved authorize parameter is refused as invalid_request, and a non-colliding hint is still forwarded.
func (*AutheoryProvider) Discovery ¶ added in v6.1.0
func (p *AutheoryProvider) Discovery(ctx context.Context) (*ProviderMetadata, error)
Discovery fetches and verifies the upstream RFC 8414 document, then caches the result. Every published endpoint must be an https URL and the document issuer must equal the configured issuer before anything is trusted.
func (*AutheoryProvider) ExchangeCode ¶ added in v6.1.0
func (p *AutheoryProvider) ExchangeCode(ctx context.Context, req CodeExchangeRequest) (*TokenSet, error)
ExchangeCode performs the upstream authorization-code exchange with PKCE.
func (*AutheoryProvider) Refresh ¶ added in v6.1.0
func (p *AutheoryProvider) Refresh(ctx context.Context, req RefreshRequest) (*TokenSet, error)
Refresh rotates the recorded upstream material, presenting the OAuth client that owns it.
func (*AutheoryProvider) ValidateAccessToken ¶ added in v6.1.0
func (p *AutheoryProvider) ValidateAccessToken( ctx context.Context, token string, expected TokenExpectations, ) (*TokenClaims, error)
ValidateAccessToken verifies the token against the expectation set and returns safe claims. The expectation set selects (and caches) one RS256/JWKS validator, so the key cache is reused across calls with the same expectations. Any validator failure is an upstream_error: a token the upstream cannot vouch for is never accepted, and the reason is never forwarded to the client.
type AutheoryProviderOptions ¶ added in v6.1.0
type AutheoryProviderOptions struct {
// Issuer is the expected upstream issuer, for example
// https://auth.autheory.app. It is required.
Issuer string
// ClientID is the adapter's own upstream client registration id. The adapter
// never derives a request's client id from it: a request that omits its
// upstream client id fails closed with invalid_request.
ClientID string
// DiscoveryURL overrides the discovery document URL. Zero value means the
// issuer plus /.well-known/oauth-authorization-server.
DiscoveryURL string
// TokenEndpoint, JWKSURI, and AuthorizationEndpoint override the endpoints
// the discovery document publishes. Each must be an https URL.
TokenEndpoint string
JWKSURI string
AuthorizationEndpoint string
// HTTPClient is reused for every discovery, token, and JWKS call. Redirects
// are refused regardless; a caller-supplied transport keeps its own
// connection policy.
HTTPClient *http.Client
// AllowPrivateHosts disables the framework's non-public-host guard on the
// calls the adapter makes with the framework's own transport. It is off by
// default; local development and loopback test suites opt in explicitly.
AllowPrivateHosts bool
// FetchTimeout bounds one discovery, token, or JWKS call. Zero means the
// framework default shared with discovery and JWKS fetching.
FetchTimeout time.Duration
// Now overrides the clock used for token expiry and key caching. Zero means
// time.Now().UTC.
Now func() time.Time
// AllowedAuthorizedParties is an exact-match allow-list applied to the
// validated token's azp claim (falling back to client_id). It is the default
// for expectation sets that do not carry their own list.
AllowedAuthorizedParties []string
}
AutheoryProviderOptions configures NewAutheoryProvider.
type AuthorizationCodeRecord ¶
type AuthorizationCodeRecord struct {
Code string
ClientID string
RedirectURI string
Resource string
CodeChallenge string
CodeChallengeMethod string
ExpiresAt time.Time
}
AuthorizationCodeRecord stores the data needed to redeem an authorization code.
type AuthorizationCodeStore ¶
type AuthorizationCodeStore interface {
Put(ctx context.Context, rec *AuthorizationCodeRecord) error
Consume(ctx context.Context, code string) (*AuthorizationCodeRecord, error)
}
AuthorizationCodeStore stores short-lived authorization codes.
type AuthorizationRequest ¶ added in v6.1.0
type AuthorizationRequest struct {
UpstreamClientID string
CallbackURL string
CodeChallenge string
CodeChallengeMethod string
State string
Resource string
Scopes []string
Hints map[string]string
}
AuthorizationRequest is one upstream authorize request. State is a fresh upstream state distinct from the client's state, and Hints carries opaque provider hints (for example account_id): a provider derives authentication account context from its own validated binding, never from a hint.
type AuthorizationServerMetadata ¶
type AuthorizationServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
TokenEndpoint string `json:"token_endpoint,omitempty"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
// AuthorizationResponseIssParameterSupported advertises RFC 9207 `iss` on
// authorization responses. It is opt-in and false by default: omitempty
// keeps every document that does not set it byte-identical to its pre-RFC
// 9207 shape (an absent member means false, RFC 8414 section 2), so only a
// surface that provably sends `iss` sets it. The field is a typed bool, so
// the wire value is always the JSON boolean `true` while present: no
// non-boolean state can reach the document.
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}
AuthorizationServerMetadata is the RFC8414 Authorization Server metadata document.
func NewAuthorizationServerMetadata ¶
func NewAuthorizationServerMetadata(issuer string, opts ...AuthorizationServerMetadataOption) (*AuthorizationServerMetadata, error)
NewAuthorizationServerMetadata builds a Claude-compatible RFC8414 document with conventional root endpoints (/authorize, /token, /register) derived from the issuer/base URL. Additional RFC 8414 members such as revocation_endpoint and device_authorization_endpoint are opt-in via AuthorizationServerMetadataOption: the framework implements no revocation or device-flow endpoints, so the default document advertises only what it serves.
func (*AuthorizationServerMetadata) MarshalJSONBytes ¶
func (m *AuthorizationServerMetadata) MarshalJSONBytes() ([]byte, error)
MarshalJSONBytes marshals the metadata document to JSON bytes.
type AuthorizationServerMetadataOption ¶
type AuthorizationServerMetadataOption func(*AuthorizationServerMetadata, *url.URL) error
AuthorizationServerMetadataOption configures the RFC 8414 authorization server metadata document built by NewAuthorizationServerMetadata. Options run in order after the conventional fields are derived from the issuer base URL; base is the canonical issuer URL.
func WithDeviceAuthorizationEndpoint ¶
func WithDeviceAuthorizationEndpoint(explicit ...string) AuthorizationServerMetadataOption
WithDeviceAuthorizationEndpoint adds the RFC 8628 device_authorization_endpoint to the metadata document. With no argument it derives the conventional /device path from the issuer base URL; pass exactly one explicit absolute URL to point at a non-conventional path. Passing more than one explicit URL is rejected with ErrInvalidURL. The framework implements no device-flow endpoint, so this option is opt-in: the default document does not advertise one.
func WithRevocationEndpoint ¶
func WithRevocationEndpoint(explicit ...string) AuthorizationServerMetadataOption
WithRevocationEndpoint adds the RFC 8414 revocation_endpoint to the metadata document. With no argument it derives the conventional /revoke path from the issuer base URL; pass exactly one explicit absolute URL to point at a non-conventional path. Passing more than one explicit URL is rejected with ErrInvalidURL. The framework implements no revocation endpoint, so this option is opt-in: the default document does not advertise one.
type BearerTokenClaims ¶
type BearerTokenClaims struct {
Subject string
Audience string
Scopes []string
ExpiresAt time.Time
}
BearerTokenClaims is the deterministic, local claim shape AppTheory uses for contract-pinned bearer validation. Token is intentionally not included so the safe context value cannot leak credentials.
func BearerTokenClaimsFromContext ¶
func BearerTokenClaimsFromContext(c *apptheory.Context) (BearerTokenClaims, bool)
BearerTokenClaimsFromContext returns safe bearer claims set by RequireBearerTokenMiddleware when ClaimsValidator is configured.
type BearerTokenClaimsValidator ¶
type BearerTokenClaimsValidator func(ctx *apptheory.Context, token string) (*BearerTokenClaims, error)
BearerTokenClaimsValidator validates a Bearer access token and returns safe, non-secret claims for downstream handlers.
func NewJWKSValidator ¶ added in v6.1.0
func NewJWKSValidator(opts JWKSValidatorOptions) (BearerTokenClaimsValidator, error)
NewJWKSValidator returns a fail-closed RS256/JWKS claims validator.
The returned validator resolves signing keys from a bounded kid cache that RefreshCooldown rate-limits, verifies RSASSA-PKCS1-v1_5/SHA-256 signatures, and refuses the request on every doubt: a malformed token, a non-RS256 algorithm, a missing kid, an unknown or inconsistent key, a signature mismatch, an issuer mismatch, an expired/not-yet-valid token, an audience or scope mismatch, or a disallowed azp. It never returns claims for a token it could not fully verify, and it never logs the token or key material.
func NewMemoryBearerTokenValidator ¶
func NewMemoryBearerTokenValidator(records []BearerTokenRecord, opts BearerTokenValidationOptions) BearerTokenClaimsValidator
NewMemoryBearerTokenValidator returns a deterministic local bearer validator over the provided token records.
type BearerTokenRecord ¶
type BearerTokenRecord struct {
Token string
Subject string
Audience string
Scope string
Scopes []string
ExpiresAt time.Time
}
BearerTokenRecord seeds a deterministic in-memory bearer-token validator.
type BearerTokenValidationOptions ¶
type BearerTokenValidationOptions struct {
RequiredAudience string
RequiredScopes []string
// RequiredAudienceFunc returns the route-derived audience the token must be
// bound to. When set it takes precedence over RequiredAudience, so audience
// can be the exact matched route URL.
RequiredAudienceFunc func(*apptheory.Context) (string, error)
// RequiredScopesFunc returns the route-derived scope set the token must
// carry. When set it takes precedence over RequiredScopes.
RequiredScopesFunc func(*apptheory.Context) ([]string, error)
Now func() time.Time
}
BearerTokenValidationOptions configures NewMemoryBearerTokenValidator.
type BearerTokenValidator ¶
BearerTokenValidator validates a Bearer access token.
The validator receives the route-aware AppTheory context, so a validator can derive the expected issuer, audience, and scope set from the matched route instead of a process-global value.
type CodeExchangeRequest ¶ added in v6.1.0
type CodeExchangeRequest struct {
Code string
UpstreamClientID string
CallbackURL string
CodeVerifier string
}
CodeExchangeRequest is one server-side upstream code exchange.
type DynamicClientRegistrationPolicy ¶
type DynamicClientRegistrationPolicy struct {
AllowedRedirectURIs []string
RequirePublicClient bool // token_endpoint_auth_method must be "none"
RequireRefreshToken bool // grant_types must include "refresh_token" (if grant_types provided)
}
DynamicClientRegistrationPolicy controls DCR validation.
func ClaudeDynamicClientRegistrationPolicy ¶
func ClaudeDynamicClientRegistrationPolicy() DynamicClientRegistrationPolicy
ClaudeDynamicClientRegistrationPolicy returns a day-1 policy compatible with Claude connectors.
type DynamicClientRegistrationRequest ¶
type DynamicClientRegistrationRequest struct {
ClientName string `json:"client_name,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
Scope string `json:"scope,omitempty"`
}
DynamicClientRegistrationRequest is an RFC7591 client registration request.
type DynamicClientRegistrationResponse ¶
type DynamicClientRegistrationResponse struct {
ClientID string `json:"client_id"`
// ClientSecret is omitted for public clients.
ClientSecret string `json:"client_secret,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
}
DynamicClientRegistrationResponse is a minimal RFC7591 response.
type JWKSValidatorOptions ¶ added in v6.1.0
type JWKSValidatorOptions struct {
// Issuer is the expected token issuer. It is required.
Issuer string
// JWKSURI is the absolute HTTPS JSON Web Key Set URL. When it is empty,
// DiscoveryURL bootstraps it.
JWKSURI string
// DiscoveryURL is the absolute OIDC discovery document URL. It is consulted
// only when JWKSURI is empty, and its document issuer must equal Issuer
// before its jwks_uri is trusted.
DiscoveryURL string
// HTTPClient is reused for every discovery and JWKS fetch. Redirects are
// disabled regardless: a 3xx response fails closed.
HTTPClient *http.Client
// Now overrides the clock used for exp/nbf, cache expiry, and cooldown.
Now func() time.Time
// ClockSkew is the tolerance applied to exp and nbf.
ClockSkew time.Duration
// FetchTimeout bounds one discovery or JWKS fetch. Zero means the default of
// 10s, shared with the TypeScript and Python validators; an HTTPClient that
// already carries its own Timeout keeps that Timeout unless FetchTimeout is
// set explicitly.
FetchTimeout time.Duration
// AllowPrivateHosts disables the non-public-host check on the framework's
// discovery/JWKS transport. It is off by default and the framework never
// enables it: local development and the repository's loopback test suites
// opt in explicitly. A caller-supplied HTTPClient keeps its own connection
// policy either way.
AllowPrivateHosts bool
// CacheTTL is how long a fetched key stays usable before a refresh. Zero
// means the default.
CacheTTL time.Duration
// RefreshCooldown bounds how often one unknown kid may trigger fetches.
// Zero means the default.
RefreshCooldown time.Duration
// MaxRefreshPerKid bounds the refresh fetches a single unknown kid may
// trigger beyond the fetch that populates the cache. Zero means the default
// of 1.
MaxRefreshPerKid int
// MaxKidLength bounds a kid string before it may enter any cache. Zero means
// the default of 256.
MaxKidLength int
// MaxWindowEntries bounds the per-kid refresh-window map. Zero means the
// default of 1024.
MaxWindowEntries int
// MaxKeyEntries bounds the cached signing keys. Zero means the default of
// 128.
MaxKeyEntries int
// MaxUnknownKidFetches is the global key-set fetch budget inside one
// UnknownKidFetchWindow. Zero means the default of 32.
MaxUnknownKidFetches int
// UnknownKidFetchWindow is the rolling window of MaxUnknownKidFetches. Zero
// means the default of 60s.
UnknownKidFetchWindow time.Duration
// AllowedAZP is an exact-match allow-list for the token's azp claim, falling
// back to client_id. An empty list disables the check.
AllowedAZP []string
// RequiredAudience derives the route-bound audience. A non-empty result must
// equal one aud entry exactly; there is no substring or wildcard match.
RequiredAudience func(*apptheory.Context) (string, error)
// RequiredScopes derives the route-bound scope set. Every entry must be
// present in the token.
RequiredScopes func(*apptheory.Context) ([]string, error)
}
JWKSValidatorOptions configures NewJWKSValidator.
type MCPServerConfig ¶
type MCPServerConfig struct {
// MCPPath overrides the conventional /mcp endpoint path.
MCPPath string
// AuthorizationServerIssuer is the configured OAuth authorization server
// issuer advertised by RFC 9728 discovery.
AuthorizationServerIssuer string
// JWKSURI is the configured JSON Web Key Set URL used to validate access
// tokens. It is not the protected resource's own key set and is therefore
// not advertised by RFC 9728 discovery.
JWKSURI string
// AllowedHostnames is an optional trusted-host allowlist for discovery.
// When it is empty the discovery origin is derived from the
// deployment-authoritative `host` header alone, and forwarded-host headers
// are ignored. When it is non-empty, forwarded-host headers may be honored,
// but only a request whose canonical authority is on this list produces
// metadata; anything else fails closed with HTTP 400
// `{"error":"invalid_request_host"}`.
AllowedHostnames []string
}
MCPServerConfig configures AppTheory's secure MCP route bundle. The protected resource origin is derived from each request by default (from the deployment-authoritative `host` header) while the authorization server and JWKS remain install config.
type MemoryAuthorizationCodeStore ¶
type MemoryAuthorizationCodeStore struct {
// contains filtered or unexported fields
}
MemoryAuthorizationCodeStore is an in-memory AuthorizationCodeStore.
func NewMemoryAuthorizationCodeStore ¶
func NewMemoryAuthorizationCodeStore() *MemoryAuthorizationCodeStore
func (*MemoryAuthorizationCodeStore) Consume ¶
func (s *MemoryAuthorizationCodeStore) Consume(_ context.Context, code string) (*AuthorizationCodeRecord, error)
func (*MemoryAuthorizationCodeStore) Put ¶
func (s *MemoryAuthorizationCodeStore) Put(_ context.Context, rec *AuthorizationCodeRecord) error
type MemoryRefreshTokenStore ¶
type MemoryRefreshTokenStore struct {
// contains filtered or unexported fields
}
MemoryRefreshTokenStore is an in-memory RefreshTokenStore.
func NewMemoryRefreshTokenStore ¶
func NewMemoryRefreshTokenStore() *MemoryRefreshTokenStore
func (*MemoryRefreshTokenStore) Consume ¶
func (s *MemoryRefreshTokenStore) Consume(_ context.Context, token string) (*RefreshTokenRecord, error)
func (*MemoryRefreshTokenStore) Delete ¶
func (s *MemoryRefreshTokenStore) Delete(_ context.Context, token string) error
func (*MemoryRefreshTokenStore) Get ¶
func (s *MemoryRefreshTokenStore) Get(_ context.Context, token string) (*RefreshTokenRecord, error)
func (*MemoryRefreshTokenStore) Put ¶
func (s *MemoryRefreshTokenStore) Put(_ context.Context, rec *RefreshTokenRecord) error
type ProtectedResourceMetadata ¶
type ProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
JWKSURI string `json:"jwks_uri,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"`
}
ProtectedResourceMetadata is the RFC9728 discovery document hosted by a protected resource server.
func NewProtectedResourceMetadata ¶
func NewProtectedResourceMetadata(resource string, authorizationServers []string) (*ProtectedResourceMetadata, error)
NewProtectedResourceMetadata creates a minimal metadata document. It requires: - resource: an absolute URL that identifies the protected resource (for MCP this is typically the `/mcp` endpoint) - authorizationServers: one or more OAuth AS issuer/base URLs
func (*ProtectedResourceMetadata) MarshalJSONBytes ¶
func (m *ProtectedResourceMetadata) MarshalJSONBytes() ([]byte, error)
MarshalJSONBytes marshals the metadata document to JSON bytes.
type Provider ¶ added in v6.1.0
type Provider interface {
// Discovery returns the upstream metadata after verifying the discovery
// document: the document issuer must equal the configured issuer before any
// published endpoint is trusted.
Discovery(ctx context.Context) (*ProviderMetadata, error)
// AuthorizationURL builds the absolute upstream authorize URL a browser is
// redirected to from the facade's /authorize surface.
AuthorizationURL(req AuthorizationRequest) (string, error)
// ExchangeCode performs the server-side upstream code exchange, presenting
// the facade's own client and PKCE verifier.
ExchangeCode(ctx context.Context, req CodeExchangeRequest) (*TokenSet, error)
// Refresh performs an upstream refresh presenting the OAuth client that owns
// the recorded upstream material.
Refresh(ctx context.Context, req RefreshRequest) (*TokenSet, error)
// ValidateAccessToken validates an upstream access token against the caller's
// expectations and returns safe, non-secret claims. It never mutates the
// token.
ValidateAccessToken(ctx context.Context, token string, expected TokenExpectations) (*TokenClaims, error)
}
Provider is the provider-neutral upstream IdP adapter SPI of the MCP auth facade (DESIGN §6, contract fixture facade-provider-spi.v1.json).
The SPI is deliberately the minimum needed to derive the facade from the upstream requirements, so provider-specific policy travels through hints and typed errors rather than framework branches. Every method is fail-closed: it never relaxes a check, never mutates the token it is given, and never forwards an upstream response body to its caller. Failures are reported as a typed *ProviderError carrying the provider error class the facade maps onto the closed client-facing vocabulary.
type ProviderError ¶ added in v6.1.0
type ProviderError struct {
Class ProviderErrorClass
Op string
Message string
// contains filtered or unexported fields
}
ProviderError is the typed error every provider adapter returns. Message is a fixed adapter string and never contains any part of an upstream response body; the attached cause is only ever a transport failure (an errors.Is / errors.As target), so rendering an error can never echo upstream material.
func AsProviderError ¶ added in v6.1.0
func AsProviderError(err error) (*ProviderError, bool)
AsProviderError reports whether err is (or wraps) a *ProviderError.
func NewProviderError ¶ added in v6.1.0
func NewProviderError(class ProviderErrorClass, op string, message string) *ProviderError
NewProviderError returns a ProviderError for class and operation with a fixed message.
func (*ProviderError) Error ¶ added in v6.1.0
func (e *ProviderError) Error() string
Error implements error. The rendered string is the class, the operation, the fixed message, and the transport cause: no upstream response body is ever part of it.
func (*ProviderError) HTTPStatus ¶ added in v6.1.0
func (e *ProviderError) HTTPStatus() int
HTTPStatus maps the class onto the facade's client-facing status.
func (*ProviderError) Unwrap ¶ added in v6.1.0
func (e *ProviderError) Unwrap() error
Unwrap returns the underlying cause so errors.Is/errors.As can classify the transport failure that produced the class.
type ProviderErrorClass ¶ added in v6.1.0
type ProviderErrorClass string
ProviderErrorClass is the closed provider error vocabulary of DESIGN §6. It maps onto the facade's client-facing classes and HTTP statuses.
const ( // ProviderUpstreamError is an upstream provider failure (facade HTTP 502). ProviderUpstreamError ProviderErrorClass = "upstream_error" // (facade HTTP 503). ProviderTemporarilyUnavailable ProviderErrorClass = "temporarily_unavailable" // ProviderInvalidGrant means the provider definitively rejected the grant // (terminal; facade HTTP 400). ProviderInvalidGrant ProviderErrorClass = "invalid_grant" // ProviderInvalidRequest means the adapter was called with a request it // cannot send; it never reaches the upstream (facade HTTP 400). ProviderInvalidRequest ProviderErrorClass = "invalid_request" // ProviderReauthRequired means the provider reported the recorded material // stale or replaced (terminal; facade HTTP 400). ProviderReauthRequired ProviderErrorClass = "reauth_required" )
ProviderErrorClass values. Every class is reachable from an explicit adapter decision; none is inferred from a raw upstream body.
type ProviderMetadata ¶ added in v6.1.0
type ProviderMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
JWKSURI string `json:"jwks_uri"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
ResourceParameterSupported bool `json:"resource_parameter_supported"`
}
ProviderMetadata is the verified upstream discovery document subset the facade relies on.
type RefreshRequest ¶ added in v6.1.0
RefreshRequest is one upstream refresh. UpstreamClientID is the OAuth client that owns the recorded upstream material: the facade's own client for facade-opened records, the client's own id for pre-facade records.
type RefreshTokenRecord ¶
type RefreshTokenRecord struct {
Token string
ClientID string
Subject string
Resource string
ExpiresAt time.Time
}
RefreshTokenRecord stores long-lived refresh token state.
type RefreshTokenStore ¶
type RefreshTokenStore interface {
Put(ctx context.Context, rec *RefreshTokenRecord) error
Get(ctx context.Context, token string) (*RefreshTokenRecord, error)
Consume(ctx context.Context, token string) (*RefreshTokenRecord, error)
Delete(ctx context.Context, token string) error
}
RefreshTokenStore stores refresh tokens. Implementations should support rotation by Consume+Put.
type RequireBearerTokenOptions ¶
type RequireBearerTokenOptions struct {
// ResourceMetadataURL is used to build the RFC9728 discovery challenge.
//
// The middleware reads no environment variable. When this and
// ResourceMetadataURLFunc are both empty the challenge is the bare
// `Bearer` form with no resource_metadata parameter, so a missing
// configuration fails closed to an unadvertised discovery endpoint rather
// than to a value an injected MCP_ENDPOINT could influence.
ResourceMetadataURL string
// ResourceMetadataURLFunc returns the route-derived protected-resource
// metadata URL for the matched route. When set it takes precedence over
// ResourceMetadataURLTemplate and ResourceMetadataURL, so the challenge is
// route-derived rather than family-wide.
ResourceMetadataURLFunc func(*apptheory.Context) (string, error)
// ResourceMetadataURLTemplate is an absolute HTTPS URL template whose
// `{param}` placeholders are interpolated from the matched route's path
// parameters, so a per-actor route (`/mcp/{actor}`) advertises that actor's
// own RFC 9728 metadata URL with no hand-rolled resolver. It is explicit
// application configuration: the middleware reads no environment variable.
//
// Pass the CDK construct's route-derived template straight through, for
// example `AppTheoryRemoteMcpServer.protectedResourceMetadataUrl` on the
// adopter's own configuration surface.
//
// The template is validated when the middleware is constructed — it must be
// an absolute HTTPS URL with no userinfo, query, fragment, or percent
// escape and no "." or ".." path segment, and its placeholders must appear
// in the path and name route parameters. A malformed template panics at
// registration rather than advertising a broken discovery URL, so a bad
// template fails closed at startup instead of at request time.
//
// At request time a placeholder that the matched route does not supply, or
// whose value is not a safe single path segment (see
// isSafeResourceMetadataURLTemplateValue), makes the middleware emit the
// bare `Bearer` challenge. It never advertises a literal placeholder.
//
// When set it takes precedence over ResourceMetadataURL; ResourceMetadataURLFunc
// still takes precedence over it.
ResourceMetadataURLTemplate string
// Validator is called for every request. If it is omitted, or if it returns
// an error, the request is rejected with 401.
Validator BearerTokenValidator
// ClaimsValidator is preferred for new code because it can distinguish
// unauthorized token failures from forbidden audience/scope failures and
// expose safe claims to downstream handlers.
ClaimsValidator BearerTokenClaimsValidator
// RequiredScopesFunc returns the route-derived scope set used to build the
// 403 insufficient_scope remediation challenge. It is consulted only when a
// validator reports ErrBearerTokenInsufficientScope.
RequiredScopesFunc func(*apptheory.Context) ([]string, error)
// contains filtered or unexported fields
}
RequireBearerTokenOptions configures RequireBearerTokenMiddleware.
type TokenClaims ¶ added in v6.1.0
type TokenClaims struct {
Subject string `json:"subject"`
Audience string `json:"audience"`
Scopes []string `json:"scopes"`
ExpiresAt time.Time `json:"expires_at"`
AuthorizedParty string `json:"authorized_party"`
}
TokenClaims is the safe, non-secret claim subset ValidateAccessToken returns.
type TokenExpectations ¶ added in v6.1.0
type TokenExpectations struct {
ExpectedIssuer string
ExpectedAudience string
RequiredScopes []string
AllowedAuthorizedParties []string
}
TokenExpectations is the caller's expectation set for access-token validation. AllowedAuthorizedParties is an exact-match allow-list for the token's azp claim (falling back to client_id); an empty list disables that check.
type TokenSet ¶ added in v6.1.0
type TokenSet struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
IDToken string `json:"id_token"`
}
TokenSet is one upstream token response. The adapter never drops an upstream field the facade may need: the access token is returned to the client byte-for-byte unmodified, while refresh material and any id_token never reach the client.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package store defines the five pluggable store SPIs of the MCP auth facade (DESIGN §7, contract fixture facade-store-spi.v1.json) and the framework's in-memory reference implementations.
|
Package store defines the five pluggable store SPIs of the MCP auth facade (DESIGN §7, contract fixture facade-store-spi.v1.json) and the framework's in-memory reference implementations. |