Documentation
¶
Index ¶
- Variables
- func AccessRequestFromContext(ctx context.Context) fosite.AccessRequester
- func HasScope(ctx context.Context, scope string) bool
- func LoggerFromContext(ctx context.Context) *slog.Logger
- func ScopesFromContext(ctx context.Context) []string
- func UserIDFromContext(ctx context.Context) string
- func WithAccessRequest(ctx context.Context, ar fosite.AccessRequester) context.Context
- type API
- type AuthorizeInput
- type AuthorizeOutput
- type Client
- func (c *Client) DPoPBoundAccessTokens() bool
- func (c *Client) GetAudience() fosite.Arguments
- func (c *Client) GetGrantTypes() fosite.Arguments
- func (c *Client) GetHashedSecret() []byte
- func (c *Client) GetID() string
- func (c *Client) GetRedirectURIs() []string
- func (c *Client) GetResponseTypes() fosite.Arguments
- func (c *Client) GetRotatedHashes() [][]byte
- func (c *Client) GetScopes() fosite.Arguments
- func (c *Client) IsPublic() bool
- type Config
- type ConsentFunc
- type DPoPConfig
- type IntrospectInput
- type IntrospectOutput
- type IntrospectResponse
- type JWK
- type JWKS
- type JWKSOutput
- type OAuthError
- type OpenIDConfigOutput
- type OpenIDConfiguration
- type Option
- func WithAcceptedClientIDs(clientIDs ...string) Option
- func WithAnyClient() Option
- func WithConsent(consent ConsentFunc) Option
- func WithLogger(logger *slog.Logger) Option
- func WithLoginURL(loginURL string) Option
- func WithResourceBaseURL(baseURL string) Option
- func WithTrustedUserIDHeader(header string, gate systemauth.RequestGate) Option
- func WithUserAuthenticator(authenticate func(r *http.Request) string) Option
- type Provider
- type RevokeInput
- type RevokeOutput
- type Storage
- func (s *Storage) ClientAssertionJWTValid(ctx context.Context, jti string) error
- func (s *Storage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error
- func (s *Storage) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) error
- func (s *Storage) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) error
- func (s *Storage) CreateRefreshTokenSession(ctx context.Context, signature string, accessSignature string, ...) error
- func (s *Storage) DeleteAccessTokenSession(ctx context.Context, signature string) error
- func (s *Storage) DeletePKCERequestSession(ctx context.Context, signature string) error
- func (s *Storage) DeleteRefreshTokenSession(ctx context.Context, signature string) error
- func (s *Storage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
- func (s *Storage) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (fosite.Requester, error)
- func (s *Storage) GetClient(ctx context.Context, clientID string) (fosite.Client, error)
- func (s *Storage) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
- func (s *Storage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
- func (s *Storage) InvalidateAuthorizeCodeSession(ctx context.Context, code string) error
- func (s *Storage) RevokeAccessToken(ctx context.Context, requestID string) error
- func (s *Storage) RevokeRefreshToken(ctx context.Context, requestID string) error
- func (s *Storage) RotateRefreshToken(ctx context.Context, requestID string, refreshTokenSignature string) error
- func (s *Storage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error
- type TokenInput
- type TokenOutput
- type TokenResponse
Constants ¶
This section is empty.
Variables ¶
var ErrSigningKeyRequired = errors.New("oauth: a signing key (Config.PrivateKey) is required")
ErrSigningKeyRequired is returned by NewProvider when Config.PrivateKey is nil and Config.AllowEphemeralKey is not set.
Functions ¶
func AccessRequestFromContext ¶
func AccessRequestFromContext(ctx context.Context) fosite.AccessRequester
AccessRequestFromContext extracts the access request from context.
func LoggerFromContext ¶
LoggerFromContext returns the logger from context, or slog.Default() if not set.
func ScopesFromContext ¶
ScopesFromContext extracts the granted scopes from the access request in context.
func UserIDFromContext ¶
UserIDFromContext extracts the user ID (subject) from the access request in context.
func WithAccessRequest ¶
WithAccessRequest adds the access request to the context.
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API provides HTTP handlers for OAuth 2.0 endpoints using Huma/Chi. It uses a hybrid approach: discovery endpoints use Huma for full typed handling, while Fosite-integrated endpoints use Chi handlers directly.
func (*API) Middleware ¶
Middleware provides OAuth token validation middleware. It accepts an access token only when it is valid and unrevoked, was issued to a client accepted by WithAcceptedClientIDs (or WithAnyClient) that is still active, and its principal is still active, the same principal check the introspection endpoint applies.
A DPoP-bound token (RFC 9449) is accepted only with the DPoP authorization scheme and a fresh, unreplayed proof by its key for the request's method and URL, carrying the token's hash; the URL is WithResourceBaseURL (default: the issuer's origin) plus the request path. An unbound token, which only a client registered with the allow_bearer_tokens exception gets, is accepted only with the Bearer scheme. Refusals carry WWW-Authenticate challenges.
Middleware panics when the API was created with neither WithAcceptedClientIDs nor WithAnyClient.
type AuthorizeInput ¶
type AuthorizeInput struct {
ResponseType string `query:"response_type" required:"true" enum:"code,token" doc:"OAuth 2.0 response type"`
ClientID string `query:"client_id" required:"true" doc:"Client identifier"`
RedirectURI string `query:"redirect_uri" doc:"URI to redirect after authorization"`
Scope string `query:"scope" doc:"Space-separated list of requested scopes"`
State string `query:"state" doc:"Opaque value for CSRF protection"`
CodeChallenge string `query:"code_challenge" doc:"PKCE code challenge"`
CodeChallengeMethod string `query:"code_challenge_method" enum:"S256,plain" doc:"PKCE code challenge method"`
Nonce string `query:"nonce" doc:"OpenID Connect nonce for replay protection"`
}
AuthorizeInput represents the OAuth 2.0 authorization request parameters.
type AuthorizeOutput ¶
type AuthorizeOutput struct {
Location string `header:"Location" doc:"Redirect URI with authorization code or token"`
}
AuthorizeOutput represents the authorization response (redirect).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client implements fosite.Client backed by Ent OAuthApp.
func (*Client) DPoPBoundAccessTokens ¶ added in v0.21.0
DPoPBoundAccessTokens reports whether the client must use DPoP (RFC 9449's dpop_bound_access_tokens): every client except one registered with the allow_bearer_tokens legacy exception, which may get unbound bearer tokens.
func (*Client) GetAudience ¶
GetAudience returns the audience for this client.
func (*Client) GetGrantTypes ¶
GetGrantTypes returns the allowed grant types.
func (*Client) GetHashedSecret ¶
GetHashedSecret returns the hash of the client's newest active secret, or nil for a client without one.
func (*Client) GetRedirectURIs ¶
GetRedirectURIs returns the registered redirect URIs.
func (*Client) GetResponseTypes ¶
GetResponseTypes returns the allowed response types.
func (*Client) GetRotatedHashes ¶ added in v0.21.0
GetRotatedHashes returns the hashes of the client's other active secrets, accepted during a secret rotation.
type Config ¶
type Config struct {
// Issuer is the OAuth/OIDC issuer URL: the external URL clients use,
// from which the endpoint URLs DPoP proofs must name are built.
Issuer string
// AccessTokenLifespan is the duration access tokens are valid
AccessTokenLifespan time.Duration
// RefreshTokenLifespan is the duration refresh tokens are valid
RefreshTokenLifespan time.Duration
// AuthCodeLifespan is the duration authorization codes are valid
AuthCodeLifespan time.Duration
// PrivateKey is the RSA private key for signing tokens (required). It
// must be the same on every instance and across restarts: tokens and
// the JWKS are tied to it.
PrivateKey *rsa.PrivateKey
// KeyID is the "kid" of PrivateKey, published in the JWKS and set on
// every JWT. Default: the RFC 7638 thumbprint of the key.
//
// A token's kid selects the key that verifies it: KeyID selects the
// signing key, a VerificationKeys entry selects that key, and any other
// kid is refused. Rotate keeping the current key N and the previous
// key N-1, as for SystemAuth (systemauth.KeyConfig.KeyID):
//
// 1. Pre-stage: add the new key N to VerificationKeys while N-1 still
// signs, so verifiers pick it up from the JWKS first.
// 2. Promote: make N PrivateKey, move N-1 into VerificationKeys, and
// drop N-2.
//
// Never drop N-1 in the deploy that promotes N: tokens it signed are
// still in use until they expire.
KeyID string
// VerificationKeys are keys published in the JWKS and accepted when
// verifying tokens, but never used to sign: the previous signing key
// N-1 after a rotation, and a pre-staged next key before it is
// promoted.
VerificationKeys []systemauth.VerificationKeyConfig
// AllowEphemeralKey lets NewProvider generate a fresh signing key when
// PrivateKey is nil. Development and tests only: the key changes on
// every start and differs between instances, so tokens stop verifying
// after a restart.
AllowEphemeralKey bool
// JWTAccessTokens issues access tokens as RS256 JWTs (RFC 9068, typ
// at+jwt) signed with PrivateKey, carrying cnf.jkt when DPoP-bound,
// instead of opaque tokens. Either way tokens are stored, so
// revocation and introspection work.
JWTAccessTokens bool
// HashSecret is the secret used for HMAC operations
HashSecret []byte
// DPoP configures DPoP proof checking (RFC 9449).
DPoP DPoPConfig
}
Config holds OAuth 2.0 server configuration.
func DefaultConfig ¶
DefaultConfig returns a default OAuth configuration.
type ConsentFunc ¶ added in v0.14.0
type ConsentFunc func(ctx context.Context, userID string, client fosite.Client, scopes []string) (bool, error)
ConsentFunc reports whether userID has consented to client receiving scopes. It is consulted for clients that are not first-party.
type DPoPConfig ¶ added in v0.21.0
type DPoPConfig struct {
// ProofMaxAge is how old a proof's iat may be. Default: 5 minutes.
ProofMaxAge time.Duration
// ClockSkew is how far a proof's iat may be in the future, and is added
// to ProofMaxAge. Default: 30 seconds.
ClockSkew time.Duration
// ReplayCache rejects a proof whose jti was used before. Default: an
// in-memory cache, which suits a single instance; when several
// instances serve the provider, pass one they share, such as
// middleware.NewRedisReplayCache.
ReplayCache dpopproof.ReplayCache
// Nonces, when set, requires every proof to carry a server nonce
// (RFC 9449 §8); see dpopproof.NewNonces.
Nonces *dpopproof.Nonces
}
DPoPConfig configures how the provider checks DPoP proofs.
type IntrospectInput ¶
type IntrospectInput struct {
Token string `form:"token" required:"true" doc:"The token to introspect"`
TokenTypeHint string `form:"token_type_hint" enum:"access_token,refresh_token" doc:"Hint about the token type"`
// Client authentication
Authorization string `header:"Authorization" doc:"Basic authentication header (client_id:client_secret)"`
}
IntrospectInput represents the token introspection request.
type IntrospectOutput ¶
type IntrospectOutput struct {
Body IntrospectResponse
}
IntrospectOutput wraps the introspection response.
type IntrospectResponse ¶
type IntrospectResponse struct {
Active bool `json:"active" doc:"Whether the token is active"`
Scope string `json:"scope,omitempty" doc:"Scopes associated with the token"`
ClientID string `json:"client_id,omitempty" doc:"Client that requested the token"`
Username string `json:"username,omitempty" doc:"Resource owner username"`
TokenType string `json:"token_type,omitempty" doc:"Token type"`
Exp int64 `json:"exp,omitempty" doc:"Token expiration timestamp"`
Iat int64 `json:"iat,omitempty" doc:"Token issue timestamp"`
Nbf int64 `json:"nbf,omitempty" doc:"Token not-before timestamp"`
Sub string `json:"sub,omitempty" doc:"Subject (user ID)"`
Aud string `json:"aud,omitempty" doc:"Intended audience"`
Iss string `json:"iss,omitempty" doc:"Token issuer"`
Jti string `json:"jti,omitempty" doc:"JWT ID"`
}
IntrospectResponse represents the token introspection response.
type JWK ¶
type JWK struct {
Kty string `json:"kty" doc:"Key type (e.g., RSA, EC)"`
Use string `json:"use,omitempty" doc:"Key use (sig for signature, enc for encryption)"`
Kid string `json:"kid,omitempty" doc:"Key ID"`
Alg string `json:"alg,omitempty" doc:"Algorithm"`
N string `json:"n,omitempty" doc:"RSA modulus"`
E string `json:"e,omitempty" doc:"RSA exponent"`
}
JWK represents a JSON Web Key.
type JWKS ¶
type JWKS struct {
Keys []JWK `json:"keys" doc:"Array of JSON Web Keys"`
}
JWKS represents a JSON Web Key Set.
type JWKSOutput ¶
type JWKSOutput struct {
Body JWKS
}
JWKSOutput is the response for the JWKS endpoint.
type OAuthError ¶
type OAuthError struct {
Error string `json:"error" doc:"Error code"`
ErrorDescription string `json:"error_description,omitempty" doc:"Human-readable error description"`
ErrorURI string `json:"error_uri,omitempty" doc:"URI with more information about the error"`
}
OAuthError represents an OAuth 2.0 error response.
type OpenIDConfigOutput ¶
type OpenIDConfigOutput struct {
Body OpenIDConfiguration
}
OpenIDConfigOutput is the response for the OpenID Configuration endpoint.
type OpenIDConfiguration ¶
type OpenIDConfiguration struct {
Issuer string `json:"issuer" doc:"OAuth 2.0 Issuer Identifier"`
AuthorizationEndpoint string `json:"authorization_endpoint" doc:"URL of the authorization endpoint"`
TokenEndpoint string `json:"token_endpoint" doc:"URL of the token endpoint"`
IntrospectionEndpoint string `json:"introspection_endpoint" doc:"URL of the introspection endpoint"`
RevocationEndpoint string `json:"revocation_endpoint" doc:"URL of the revocation endpoint"`
JWKSURI string `json:"jwks_uri" doc:"URL of the JSON Web Key Set document"`
ResponseTypesSupported []string `json:"response_types_supported" doc:"List of supported response types"`
GrantTypesSupported []string `json:"grant_types_supported" doc:"List of supported grant types"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported" doc:"List of supported token endpoint authentication methods"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported" doc:"List of supported PKCE code challenge methods"`
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported" doc:"JWS algorithms accepted for DPoP proofs (RFC 9449)"`
}
OpenIDConfiguration represents the OpenID Connect discovery document.
type Option ¶
type Option func(*API)
Option configures an API.
func WithAcceptedClientIDs ¶ added in v0.21.0
WithAcceptedClientIDs sets the OAuth clients whose access tokens Middleware accepts. A token issued to any other client is refused, so a client a user authorized for one purpose cannot call APIs meant for another.
func WithAnyClient ¶ added in v0.21.0
func WithAnyClient() Option
WithAnyClient makes Middleware accept access tokens issued to any active client of this server. Use it only when every client may call the protected API and scopes alone limit what each may do.
func WithConsent ¶ added in v0.14.0
func WithConsent(consent ConsentFunc) Option
WithConsent sets how consent is checked for clients that are not first-party. Without it such clients are refused with access_denied; first-party clients never need consent.
func WithLogger ¶
WithLogger sets the logger for the API. If not set, slog.Default() is used.
func WithLoginURL ¶ added in v0.14.0
WithLoginURL sets where unauthenticated users are sent; the original authorization request is passed back in the redirect query parameter. Without it, an unauthenticated authorization request is answered with login_required.
func WithResourceBaseURL ¶ added in v0.21.0
WithResourceBaseURL sets the external base URL of the routes behind Middleware, such as "https://api.example.com" or, behind a proxy that strips a path prefix, "https://example.com/api". A DPoP proof's htu must equal it plus the request path as this server sees it. Default: the issuer's origin (scheme and host). Forwarded headers are never trusted.
func WithTrustedUserIDHeader ¶ added in v0.14.0
func WithTrustedUserIDHeader(header string, gate systemauth.RequestGate) Option
WithTrustedUserIDHeader authenticates users from a header written by a gateway that has already authenticated them (a backend-for-frontend). Only requests the gate admits are believed; see systemauth.TrustedProxyNetworks and systemauth.TrustAllRequests. A nil gate or an empty header trusts nothing.
func WithUserAuthenticator ¶ added in v0.14.0
WithUserAuthenticator sets how the authorization endpoint learns who the user is: it returns the authenticated user's ID for the request, or "" when there is none, for example by validating a session cookie. Until one is set, the authorization endpoint refuses every request with server_error rather than guess.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider wraps Fosite and provides OAuth 2.0/OIDC functionality.
It issues DPoP-bound tokens (RFC 9449) by default, as SystemAuth does: every token request must carry a valid DPoP proof, the access token is bound to the proof's key (cnf.jkt, token_type DPoP), and so is the refresh token. Only a client registered with the allow_bearer_tokens exception may call the token endpoint without a proof, and then gets unbound bearer tokens.
func NewProvider ¶
NewProvider creates a new OAuth provider.
func (*Provider) OAuth2Provider ¶
func (p *Provider) OAuth2Provider() fosite.OAuth2Provider
OAuth2Provider returns the underlying Fosite provider.
func (*Provider) Session ¶
func (p *Provider) Session(subject string) *systemauth.Session
Session creates a new OAuth session for subject. The subject is set on the session itself, which Fosite reads for introspection and token storage, as well as on the ID token claims. The session carries the DPoP binding of the grant (DPoPJKT) and, with JWTAccessTokens, the claims and header of JWT access tokens.
func (*Provider) TokenEndpointURL ¶ added in v0.21.0
TokenEndpointURL is the token endpoint as advertised in discovery: the htu a proof for a token request must name.
type RevokeInput ¶
type RevokeInput struct {
Token string `form:"token" required:"true" doc:"The token to revoke"`
TokenTypeHint string `form:"token_type_hint" enum:"access_token,refresh_token" doc:"Hint about the token type"`
// Client authentication
Authorization string `header:"Authorization" doc:"Basic authentication header (client_id:client_secret)"`
}
RevokeInput represents the token revocation request.
type RevokeOutput ¶
type RevokeOutput struct{}
RevokeOutput represents the token revocation response (empty on success).
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage implements the Fosite storage interfaces using Ent.
Clients are this package's Client. Authorization codes, tokens, PKCE, and revocation are delegated to SystemAuth's storage over the same tables, which stores each grant's full request and session, so a code exchange keeps the subject, the PKCE challenge, and the redirect URI it was issued with, and refresh-token reuse revokes the token family.
func NewStorage ¶
NewStorage creates a new Fosite storage backed by Ent.
func (*Storage) ClientAssertionJWTValid ¶ added in v0.14.0
ClientAssertionJWTValid reports fosite.ErrJTIKnown for a client-assertion JWT ID that is already used.
func (*Storage) CreateAccessTokenSession ¶
func (s *Storage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error
CreateAccessTokenSession stores an access token with its request.
func (*Storage) CreateAuthorizeCodeSession ¶
func (s *Storage) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) error
CreateAuthorizeCodeSession stores an authorization code with its request.
func (*Storage) CreatePKCERequestSession ¶
func (s *Storage) CreatePKCERequestSession(ctx context.Context, signature string, request fosite.Requester) error
CreatePKCERequestSession records a code's PKCE challenge.
func (*Storage) CreateRefreshTokenSession ¶
func (s *Storage) CreateRefreshTokenSession(ctx context.Context, signature string, accessSignature string, request fosite.Requester) error
CreateRefreshTokenSession stores a refresh token, on the paired access token's row when there is one.
func (*Storage) DeleteAccessTokenSession ¶
DeleteAccessTokenSession removes an access token session.
func (*Storage) DeletePKCERequestSession ¶
DeletePKCERequestSession is a no-op: the challenge lives on the authorization code, which the code exchange invalidates.
func (*Storage) DeleteRefreshTokenSession ¶
DeleteRefreshTokenSession revokes a refresh token.
func (*Storage) GetAccessTokenSession ¶
func (s *Storage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
GetAccessTokenSession restores the request an access token was issued for.
func (*Storage) GetAuthorizeCodeSession ¶
func (s *Storage) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (fosite.Requester, error)
GetAuthorizeCodeSession restores the request an authorization code was issued for. A code already exchanged returns the request with fosite.ErrInvalidatedAuthorizeCode, so Fosite revokes its tokens.
func (*Storage) GetClient ¶
GetClient loads an active OAuth client by its client_id, with the hashes of its active secrets for client authentication.
func (*Storage) GetPKCERequestSession ¶
func (s *Storage) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
GetPKCERequestSession restores a code's request with its PKCE challenge.
func (*Storage) GetRefreshTokenSession ¶
func (s *Storage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
GetRefreshTokenSession restores the request a refresh token was issued for. A rotated refresh token returns the request with fosite.ErrInactiveToken, so Fosite revokes the whole token family.
func (*Storage) InvalidateAuthorizeCodeSession ¶
InvalidateAuthorizeCodeSession marks an authorization code as used.
func (*Storage) RevokeAccessToken ¶
RevokeAccessToken revokes every token of the family identified by the Fosite request ID.
func (*Storage) RevokeRefreshToken ¶
RevokeRefreshToken revokes every token of the family identified by the Fosite request ID.
type TokenInput ¶
type TokenInput struct {
GrantType string `form:"grant_type" required:"true" enum:"authorization_code,refresh_token,client_credentials" doc:"OAuth 2.0 grant type"`
Code string `form:"code" doc:"Authorization code (for authorization_code grant)"`
RedirectURI string `form:"redirect_uri" doc:"Redirect URI (must match authorization request)"`
ClientID string `form:"client_id" doc:"Client identifier (if not using Basic auth)"`
ClientSecret string `form:"client_secret" doc:"Client secret (if not using Basic auth)"`
RefreshToken string `form:"refresh_token" doc:"Refresh token (for refresh_token grant)"`
Scope string `form:"scope" doc:"Requested scopes (for refresh_token or client_credentials)"`
CodeVerifier string `form:"code_verifier" doc:"PKCE code verifier"`
// Basic auth credentials (alternative to form-based client auth)
Authorization string `header:"Authorization" doc:"Basic authentication header (client_id:client_secret)"`
}
TokenInput represents the OAuth 2.0 token request parameters. Field names follow OAuth 2.0 specification (RFC 6749).
type TokenOutput ¶
type TokenOutput struct {
Body TokenResponse
}
TokenOutput wraps the token response.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token" doc:"The access token"`
TokenType string `json:"token_type" doc:"Token type (typically 'Bearer')"`
ExpiresIn int `json:"expires_in,omitempty" doc:"Token lifetime in seconds"`
RefreshToken string `json:"refresh_token,omitempty" doc:"Refresh token for obtaining new access tokens"`
Scope string `json:"scope,omitempty" doc:"Granted scopes (may differ from requested)"`
IDToken string `json:"id_token,omitempty" doc:"OpenID Connect ID token"`
}
TokenResponse represents the OAuth 2.0 token response. Field names follow OAuth 2.0 specification (RFC 6749).