Documentation
¶
Overview ¶
Package oauthserver is a generic OAuth 2.0 authorization server.
The caller plugs in session management, consent UI rendering, and token storage via interfaces (SessionManager, AuthorizationUI, Storage). Dependency-free.
Index ¶
- Constants
- func BearerClaimsFromContext(ctx context.Context) (*oauth.BearerClaims, bool)
- func DPoPAccessTokenHash(accessToken string) string
- func DPoPProof(r *http.Request) (*DPoPHeader, *DPoPClaims, error)
- func GenerateState() (string, error)
- func JWKThumbprint(jwk *oauth.JWK) (string, error)
- func NewBearerClaimsContext(ctx context.Context, claims *oauth.BearerClaims) context.Context
- func SignState(state string, secret []byte) string
- func ValidateState(cookie string, secret []byte) (string, bool)
- func VerifyDPoPProof(r *http.Request, expectedHTU string, header *DPoPHeader, claims *DPoPClaims, ...) error
- func VerifyDPoPProofTokenEndpoint(r *http.Request, expectedHTU string, header *DPoPHeader, claims *DPoPClaims, ...) error
- type AccessTokenService
- func (s *AccessTokenService) IssueAccessToken(issuer string, user oauth.User, audience, scope, grantID, clientID string) (string, error)
- func (s *AccessTokenService) IssueAccessTokenTTL(issuer string, user oauth.User, audience, scope, grantID, clientID string, ...) (string, error)
- func (s *AccessTokenService) IssueClientCredentialsAccessToken(issuer, clientID, audience, scope, grantID, dpopJKT string) (string, error)
- func (s *AccessTokenService) IssueDPoPAccessToken(issuer string, user oauth.User, ...) (string, error)
- func (s *AccessTokenService) IssueRegistrationAccessToken(issuer, clientID string) (string, error)
- func (s *AccessTokenService) JWK() []oauth.JWK
- func (s *AccessTokenService) RotateKey() (string, error)
- func (s *AccessTokenService) RotateKeyWithAlg(alg string) (string, error)
- func (s *AccessTokenService) VerifyAccessToken(token, issuer, audience string, now time.Time, touchGrant GrantTouchFunc, ...) (*oauth.BearerClaims, error)
- func (s *AccessTokenService) VerifyRegistrationAccessToken(token, issuer, audience string, now time.Time) (clientID string, err error)
- type AuditRecorder
- type AuthorizationUI
- type Client
- type ClientMetadataNetwork
- type ClientProvenance
- type Code
- type ConsentPageData
- type ConsentParams
- type DPoPClaims
- type DPoPHeader
- type DPoPJTICache
- type DPoPNonceManager
- type DeviceCode
- type Grant
- type GrantTouchFunc
- type IntrospectionAuthenticator
- type IntrospectionPrincipal
- type RateLimiter
- type RefreshToken
- type ScopeItem
- type Server
- func (s *Server) BearerAuth(next http.Handler) http.Handler
- func (s *Server) Close()
- func (s *Server) IssueNarrowToken(user oauth.User, audience, scope string, ttl time.Duration) (string, error)
- func (s *Server) ListUserGrants(userID string) []Grant
- func (s *Server) RegisterWellKnownRoutes(mux *http.ServeMux)
- func (s *Server) RevokeAllUserGrants(userID string) error
- func (s *Server) RevokeUserGrant(userID, grantID string) (bool, error)
- func (s *Server) Routes() http.Handler
- type ServerConfig
- type SessionManager
- type Store
- func (s *Store) ListUserGrants(userID string) []Grant
- func (s *Store) Path() string
- func (s *Store) PruneExpiredRefreshTokens(now time.Time) bool
- func (s *Store) RevokeAllUserGrants(userID string, now time.Time) bool
- func (s *Store) RevokeUserGrant(userID, grantID string, now time.Time) bool
- func (s *Store) Save() error
Constants ¶
const DPoPTokenType = "DPoP"
DPoPTokenType is the "token_type" value returned for DPoP-bound access tokens.
Variables ¶
This section is empty.
Functions ¶
func BearerClaimsFromContext ¶
func BearerClaimsFromContext(ctx context.Context) (*oauth.BearerClaims, bool)
BearerClaimsFromContext returns verified bearer-token claims from ctx.
func DPoPAccessTokenHash ¶
DPoPAccessTokenHash computes the DPoP ath claim (SHA-256 hash of access token base64url).
func DPoPProof ¶
func DPoPProof(r *http.Request) (*DPoPHeader, *DPoPClaims, error)
DPoPProof extracts and parses the DPoP proof from the request's DPoP header. Returns the parsed header, claims, and nil error on success.
func GenerateState ¶
GenerateState returns a 16-byte random hex string.
func JWKThumbprint ¶
JWKThumbprint returns the base64url-encoded SHA-256 thumbprint of a oauth.JWK per RFC 7638.
Supported key types: RSA, EC (P-256/P-384/P-521), OKP (Ed25519).
func NewBearerClaimsContext ¶
NewBearerClaimsContext returns a context with claims attached.
func ValidateState ¶
ValidateState splits cookie value on ".", re-computes HMAC, returns the bare state string. Returns ("", false) on any mismatch.
func VerifyDPoPProof ¶
func VerifyDPoPProof(r *http.Request, expectedHTU string, header *DPoPHeader, claims *DPoPClaims, maxAgeDur time.Duration, accessToken string, nonceCheck, jtiCheck func(string) bool) error
VerifyDPoPProof validates a DPoP proof per RFC 9449 §4.3.
accessToken is the bound access token (needed for ath validation). nonceCheck is called when a nonce is required; it returns true if the nonce is valid. jtiCheck, when non-nil, records the proof's jti and returns false on a replay; it is supplied only on the resource-server path, where a non-empty jti is also required. This function is called for resource-server side proof validation.
func VerifyDPoPProofTokenEndpoint ¶
func VerifyDPoPProofTokenEndpoint(r *http.Request, expectedHTU string, header *DPoPHeader, claims *DPoPClaims, maxAgeDur time.Duration, nonceCheck func(string) bool) error
VerifyDPoPProofTokenEndpoint validates a DPoP proof at the token endpoint.
For the token endpoint, ath validation is typically not performed (the token is being created), and nonce is validated if a nonce manager is available. jti replay tracking is deliberately omitted: single-use codes and refresh tokens already bound the token endpoint against replay.
Types ¶
type AccessTokenService ¶
type AccessTokenService struct {
// contains filtered or unexported fields
}
AccessTokenService signs and verifies OAuth JWT access tokens.
func NewAccessTokenService ¶
func NewAccessTokenService(keyPEM []byte, kid string, ttl time.Duration) (*AccessTokenService, error)
NewAccessTokenService returns an access-token service from configured key material.
keyPEM must hold a PEM-encoded RSA or EC private key; NewAccessTokenService errors if it is empty. Persistent key material keeps issued tokens valid across restarts and keeps JWKS consumers stable (RFC 9068). If kid is empty, a random key ID is generated.
func (*AccessTokenService) IssueAccessToken ¶
func (s *AccessTokenService) IssueAccessToken(issuer string, user oauth.User, audience, scope, grantID, clientID string) (string, error)
IssueAccessToken signs a JWT access token for user using the service TTL.
func (*AccessTokenService) IssueAccessTokenTTL ¶
func (s *AccessTokenService) IssueAccessTokenTTL(issuer string, user oauth.User, audience, scope, grantID, clientID string, ttl time.Duration) (string, error)
IssueAccessTokenTTL signs a JWT access token for user with an explicit TTL. It supports narrow, short-lived tokens for host capabilities that are not the configured protected resource.
func (*AccessTokenService) IssueClientCredentialsAccessToken ¶
func (s *AccessTokenService) IssueClientCredentialsAccessToken(issuer, clientID, audience, scope, grantID, dpopJKT string) (string, error)
IssueClientCredentialsAccessToken signs a machine-to-machine JWT access token for an OAuth client authenticated with the client-credentials grant. The subject is the client ID, not a user, so the token carries no username and no session applies. A non-empty dpopJKT binds the token to the client's DPoP key (RFC 9449).
func (*AccessTokenService) IssueDPoPAccessToken ¶
func (s *AccessTokenService) IssueDPoPAccessToken(issuer string, user oauth.User, audience, scope, grantID, dpopJKT, clientID string) (string, error)
IssueDPoPAccessToken signs a DPoP-bound JWT access token with cnf.jkt.
func (*AccessTokenService) IssueRegistrationAccessToken ¶
func (s *AccessTokenService) IssueRegistrationAccessToken(issuer, clientID string) (string, error)
IssueRegistrationAccessToken issues a JWT for client registration management (RFC 7592). It remains valid for the durable registration's practical lifetime; deleting the registration or retiring the signing key ends its authority.
func (*AccessTokenService) JWK ¶
func (s *AccessTokenService) JWK() []oauth.JWK
JWK returns all active public signing keys as JWKs.
func (*AccessTokenService) RotateKey ¶
func (s *AccessTokenService) RotateKey() (string, error)
RotateKey rotates a standalone, in-memory service to a new ECDSA P-256 key. Server instances instead reconcile configured keys through the durable store.
func (*AccessTokenService) RotateKeyWithAlg ¶
func (s *AccessTokenService) RotateKeyWithAlg(alg string) (string, error)
RotateKeyWithAlg rotates a standalone, in-memory service to a new key for alg. Previous keys remain available only for the access-token validation window; server instances use the durable store-backed lifecycle instead.
func (*AccessTokenService) VerifyAccessToken ¶
func (s *AccessTokenService) VerifyAccessToken(token, issuer, audience string, now time.Time, touchGrant GrantTouchFunc, session SessionManager) (*oauth.BearerClaims, error)
VerifyAccessToken validates token and returns its bearer claims.
func (*AccessTokenService) VerifyRegistrationAccessToken ¶
func (s *AccessTokenService) VerifyRegistrationAccessToken(token, issuer, audience string, now time.Time) (clientID string, err error)
VerifyRegistrationAccessToken validates a registration access token and returns the client ID from the subject claim.
type AuditRecorder ¶
type AuditRecorder interface {
RecordOAuth(ctx context.Context, userID, operation, name, decision, status string, args any)
}
AuditRecorder records OAuth authorization-server decisions.
type AuthorizationUI ¶
type AuthorizationUI interface {
LoginStartURL(r *http.Request) string
ProviderLabel(provider string) string
RenderOAuthConsent(w http.ResponseWriter, data *ConsentPageData) error
}
AuthorizationUI renders the OAuth authorization user interface.
type Client ¶
type Client struct {
ID string `json:"id"`
Name string `json:"name"`
RedirectURIs []string `json:"redirectURIs"`
TokenEndpointAuthMethod string `json:"tokenEndpointAuthMethod"`
GrantTypes []string `json:"grantTypes,omitempty"`
CreatedAt time.Time `json:"createdAt"`
Provenance ClientProvenance `json:"provenance,omitempty"`
JWKS []oauth.JWK `json:"-"`
JWKSURI string `json:"-"`
}
Client is an OAuth client established by dynamic registration or verified metadata.
type ClientMetadataNetwork ¶
type ClientMetadataNetwork interface {
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
ClientMetadataNetwork resolves and dials remote Client ID Metadata Document hosts. Implementations must not apply an HTTP proxy or perform additional name resolution.
type ClientProvenance ¶
type ClientProvenance string
ClientProvenance records how the authorization server established a client identity.
const ( // ClientProvenanceDynamic identifies a locally persisted RFC 7591 registration. ClientProvenanceDynamic ClientProvenance = "dynamic_registration" // ClientProvenanceMetadata identifies a remotely verified Client ID Metadata Document. ClientProvenanceMetadata ClientProvenance = "client_id_metadata_document" )
type Code ¶
type Code struct {
UserID string `json:"userID"`
ClientID string `json:"clientID"`
RedirectURI string `json:"redirectURI"`
CodeChallenge string `json:"codeChallenge"`
Resource string `json:"resource"`
Scope string `json:"scope"`
ExpiresAt time.Time `json:"expiresAt"`
}
Code is an issued authorization code with PKCE binding.
type ConsentPageData ¶
type ConsentPageData struct {
Action string
ConsentToken string
ClientName string
ClientID string
ClientHostname string
ClientMetadataDocument bool
RedirectURI string
Username string
UserInitial string
ProviderLabel string
Resource string
ScopeItems []ScopeItem
}
ConsentPageData is the server-rendered OAuth consent page model.
type ConsentParams ¶
type ConsentParams struct {
UserID string `json:"userID"`
Params map[string]string `json:"params"`
ExpiresAt time.Time `json:"expiresAt"`
}
ConsentParams holds in-progress OAuth authorization consent state.
type DPoPClaims ¶
type DPoPClaims struct {
JTI string `json:"jti"`
HTM string `json:"htm"`
HTU string `json:"htu"`
IAT int64 `json:"iat"`
ATH string `json:"ath,omitempty"`
Nonce string `json:"nonce,omitempty"`
}
DPoPClaims are the claims in a DPoP proof JWT payload.
type DPoPHeader ¶
type DPoPHeader struct {
Typ string `json:"typ"`
Alg string `json:"alg"`
JWK oauth.JWK `json:"jwk"`
}
DPoPHeader is the JOSE header of a DPoP proof JWT.
type DPoPJTICache ¶
type DPoPJTICache struct {
// contains filtered or unexported fields
}
DPoPJTICache tracks recently seen DPoP proof jti values to reject replays.
The resource server checks and inserts each proof's jti; a duplicate within the TTL window is a replay. Entries expire after ttl (the DPoP proof max age) because a proof older than that is already rejected on its iat.
func NewDPoPJTICache ¶
func NewDPoPJTICache(ttl time.Duration) *DPoPJTICache
NewDPoPJTICache returns a jti replay cache with the given entry TTL.
func (*DPoPJTICache) CheckAndStore ¶
func (c *DPoPJTICache) CheckAndStore(jti string) bool
CheckAndStore records jti and reports whether it is fresh.
It returns true the first time a jti is seen and false on any subsequent sighting within the TTL window (a replay).
type DPoPNonceManager ¶
type DPoPNonceManager struct {
// contains filtered or unexported fields
}
DPoPNonceManager manages server-provided DPoP nonces per RFC 9449 §8.
Nonces are one-time-use with a configurable TTL.
func NewDPoPNonceManager ¶
func NewDPoPNonceManager(ttl time.Duration) *DPoPNonceManager
NewDPoPNonceManager returns a DPoP nonce manager.
func (*DPoPNonceManager) HasNonce ¶
func (m *DPoPNonceManager) HasNonce(nonce string) bool
HasNonce returns true if the nonce exists and is not expired (does not consume).
func (*DPoPNonceManager) Issue ¶
func (m *DPoPNonceManager) Issue() (string, error)
Issue returns a fresh nonce stored with expiration.
It fails closed (RFC 9449 §8): if the system RNG fails, it returns an error rather than a guessable fallback value. Callers must surface the failure (HTTP 500 on the token path, or omit the DPoP-Nonce header on challenge paths) and never emit a weak nonce.
func (*DPoPNonceManager) Validate ¶
func (m *DPoPNonceManager) Validate(nonce string) bool
Validate checks a nonce and removes it (one-time use). Returns true if the nonce was valid and unused.
type DeviceCode ¶
type DeviceCode struct {
DeviceCode string `json:"-"`
UserCode string `json:"-"`
UserCodeKey string `json:"userCodeKey,omitempty"`
ClientID string `json:"clientID"`
Scope string `json:"scope"`
UserID string `json:"userID,omitempty"`
Status string `json:"status"`
ExpiresAt time.Time `json:"expiresAt"`
IssuedAt time.Time `json:"issuedAt"`
}
DeviceCode holds an in-progress device authorization flow (RFC 8628).
func (*DeviceCode) UnmarshalJSON ¶
func (d *DeviceCode) UnmarshalJSON(data []byte) error
UnmarshalJSON migrates legacy plaintext user codes to digest-only storage.
type Grant ¶
type Grant struct {
ID string `json:"id"`
UserID string `json:"userID"`
ClientID string `json:"clientID"`
ClientName string `json:"clientName"`
Resource string `json:"resource"`
Scope string `json:"scope"`
CreatedAt time.Time `json:"createdAt"`
LastUsedAt time.Time `json:"lastUsedAt,omitzero"`
ExpiresAt time.Time `json:"expiresAt"`
RevokedAt time.Time `json:"revokedAt,omitzero"`
}
Grant ties a user authorization grant to a client and token.
type GrantTouchFunc ¶
GrantTouchFunc marks or validates an OAuth grant during bearer-token verification. Returns (active, clientID, error).
type IntrospectionAuthenticator ¶
type IntrospectionAuthenticator func(*http.Request) (IntrospectionPrincipal, bool)
IntrospectionAuthenticator authenticates an introspection caller.
type IntrospectionPrincipal ¶
type IntrospectionPrincipal struct {
ClientID string
}
IntrospectionPrincipal identifies an authenticated introspection caller. ClientID restricts the caller to tokens issued to that OAuth client. An empty ClientID authorizes a trusted protected resource to inspect any token.
type RateLimiter ¶
RateLimiter allows or rejects a rate-limit key.
Keys are opaque strings built by the oauth package: "ip:<addr>" for per-IP limits and "client:<client_id>" for per-client limits. Introspection uses the authenticated client identity when one is scoped and otherwise falls back to the remote address. The implementation chooses the window size and request budget per key.
type RefreshToken ¶
type RefreshToken struct {
GrantID string `json:"grantID"`
UserID string `json:"userID"`
ClientID string `json:"clientID"`
Resource string `json:"resource"`
Scope string `json:"scope"`
DPoPJKT string `json:"dpopJKT,omitempty"`
ExpiresAt time.Time `json:"expiresAt"`
UsedAt time.Time `json:"usedAt,omitzero"`
RevokedAt time.Time `json:"revokedAt,omitzero"`
}
RefreshToken is an opaque refresh token persisted by hash.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server stores OAuth state for remote clients.
Clients use Authorization Code with PKCE S256. Authorization requires a web session, so unauthenticated browser requests can be sent through a caller supplied login flow before consent is shown. Access tokens are resource-scoped JWTs. Refresh tokens are resource-scoped opaque tokens, persisted as hashes, and rotated on every refresh grant.
func NewServer ¶
func NewServer(c ServerConfig) (*Server, error)
NewServer returns an OAuth authorization server.
func (*Server) BearerAuth ¶
BearerAuth verifies OAuth bearer tokens. On success, bearer claims and the verified user are set in the request context using the configured callbacks.
func (*Server) Close ¶
func (s *Server) Close()
Close releases exclusive ownership of the configured durable state path.
func (*Server) IssueNarrowToken ¶
func (s *Server) IssueNarrowToken(user oauth.User, audience, scope string, ttl time.Duration) (string, error)
IssueNarrowToken signs a short-lived access token for user with an explicit audience and scope. Hosts use it for host-authenticated, narrow capabilities (such as the voice gateway) that are not the server's protected resource. The token carries no grant, so it cannot be refreshed or introspected as a client grant, and this server's own BearerAuth rejects it because its audience differs from the configured protected resource.
func (*Server) ListUserGrants ¶
ListUserGrants returns a user's grants, newest first.
func (*Server) RegisterWellKnownRoutes ¶
RegisterWellKnownRoutes registers OAuth metadata endpoints on mux.
func (*Server) RevokeAllUserGrants ¶
RevokeAllUserGrants revokes all grants and refresh tokens for a user, then saves durable state.
func (*Server) RevokeUserGrant ¶
RevokeUserGrant revokes one user's grant and all refresh tokens for it, then saves durable state.
type ServerConfig ¶
type ServerConfig struct {
KeyPEM []byte
KeyID string
Issuer string
AccessTokenTTL time.Duration
AuthCodeTTL time.Duration
RefreshTokenTTL time.Duration
DPoPNonceTTL time.Duration
// RefreshTokenStorePath is exclusively owned by one Server until Close.
// Deployments must not share the same JSON state path across processes.
RefreshTokenStorePath string
ResourceURLPath string
ResourceMetadataURLPath string
ClientIDPrefix string
SupportedScopes []string
DefaultScopes []string
ScopeLabels map[string]string
Session SessionManager
UI AuthorizationUI
Audit AuditRecorder
RateLimiter RateLimiter
IntrospectionAuth IntrospectionAuthenticator
ClientMetadataNetwork ClientMetadataNetwork
ClientMetadataRootCAs *x509.CertPool
}
ServerConfig configures an OAuth authorization server.
The package owns protocol behavior. Callers inject product seams for login, users, audit, rate limiting, base URL discovery, and consent rendering so package oauth never depends on caic auth, MCP, API, or UI packages.
type SessionManager ¶
type SessionManager interface {
CurrentUser(ctx context.Context) (oauth.User, bool)
AttachUser(ctx context.Context, u oauth.User) context.Context
FindUser(id string) (oauth.User, bool)
EndSession(ctx context.Context, r *http.Request, u oauth.User) (redirectURL string)
}
SessionManager manages user sessions for the authorization server. The caller implements browser login, user lookup, and session teardown.
type Store ¶
type Store struct {
Clients map[string]Client `json:"clients,omitempty"`
RefreshTokens map[string]RefreshToken `json:"refreshTokens,omitempty"`
Grants map[string]Grant `json:"grants,omitempty"`
Codes map[string]Code `json:"codes,omitempty"`
Consents map[string]ConsentParams `json:"consents,omitempty"`
DeviceCodes map[string]*DeviceCode `json:"deviceCodes,omitempty"`
DPoPProofs map[string]time.Time `json:"dpopProofs,omitempty"`
DPoPNonces map[string]time.Time `json:"dpopNonces,omitempty"`
ClientAssertionJTIs map[string]time.Time `json:"clientAssertionJTIs,omitempty"`
// contains filtered or unexported fields
}
Store holds durable OAuth clients, refresh tokens, grants, authorization codes, and consents.
func (*Store) ListUserGrants ¶
ListUserGrants returns a user's grants, newest first.
func (*Store) PruneExpiredRefreshTokens ¶
PruneExpiredRefreshTokens removes expired refresh tokens.
func (*Store) RevokeAllUserGrants ¶
RevokeAllUserGrants revokes all grants and refresh tokens for a user. Returns true if any grants were revoked.
func (*Store) RevokeUserGrant ¶
RevokeUserGrant revokes one user's grant and all refresh tokens for it.