oauthserver

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

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

View Source
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

func DPoPAccessTokenHash(accessToken string) string

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

func GenerateState() (string, error)

GenerateState returns a 16-byte random hex string.

func JWKThumbprint

func JWKThumbprint(jwk *oauth.JWK) (string, error)

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

func NewBearerClaimsContext(ctx context.Context, claims *oauth.BearerClaims) context.Context

NewBearerClaimsContext returns a context with claims attached.

func SignState

func SignState(state string, secret []byte) string

SignState returns "state.hex(HMAC-SHA256(secret, state))".

func ValidateState

func ValidateState(cookie string, secret []byte) (string, bool)

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
	Name                    string
	RedirectURIs            []string
	TokenEndpointAuthMethod string
	GrantTypes              []string
	CreatedAt               time.Time
	Provenance              ClientProvenance
	JWKS                    []oauth.JWK
	JWKSURI                 string
}

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 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 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
	UserCode    string
	UserCodeKey string
	ClientID    string
	Scope       string
	UserID      string
	Status      string
	ExpiresAt   time.Time
	IssuedAt    time.Time
}

DeviceCode holds an in-progress device authorization flow (RFC 8628).

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 describes a user authorization returned by ListUserGrants.

type GrantTouchFunc

type GrantTouchFunc func(grantID string, now time.Time) (active bool, clientID string, err error)

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

type RateLimiter interface {
	Allow(key string) bool
}

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 ScopeItem

type ScopeItem struct {
	ID      string
	Label   string
	Checked bool
}

ScopeItem holds a scope identifier and its human-readable label.

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

func (s *Server) BearerAuth(next http.Handler) http.Handler

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

func (s *Server) ListUserGrants(userID string) []Grant

ListUserGrants returns a user's grants, newest first.

func (*Server) RegisterWellKnownRoutes

func (s *Server) RegisterWellKnownRoutes(mux *http.ServeMux)

RegisterWellKnownRoutes registers OAuth metadata endpoints on mux.

func (*Server) RevokeAllUserGrants

func (s *Server) RevokeAllUserGrants(userID string) error

RevokeAllUserGrants revokes all grants and refresh tokens for a user, then saves durable state.

func (*Server) RevokeUserGrant

func (s *Server) RevokeUserGrant(userID, grantID string) (bool, error)

RevokeUserGrant revokes one user's grant and all refresh tokens for it, then saves durable state.

func (*Server) Routes

func (s *Server) Routes() http.Handler

Routes returns OAuth authorization-server endpoint routes.

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
	RefreshTokens       map[string]v5.RefreshToken
	Grants              map[string]v5.Grant
	Codes               map[string]v5.Code
	Consents            map[string]v5.ConsentParams
	DeviceCodes         map[string]*DeviceCode
	DPoPProofs          map[string]time.Time
	DPoPNonces          map[string]time.Time
	ClientAssertionJTIs map[string]time.Time
	// contains filtered or unexported fields
}

Store holds durable OAuth clients, refresh tokens, grants, authorization codes, and consents.

func LoadStore

func LoadStore(path string) (*Store, error)

LoadStore loads durable OAuth state from path. Existing files must use version 5, the format used by caic v0.13.2; missing files start an empty store.

func (*Store) ListUserGrants

func (s *Store) ListUserGrants(userID string) []Grant

ListUserGrants returns a user's grants, newest first.

func (*Store) Path

func (s *Store) Path() string

Path returns the durable OAuth state path.

func (*Store) PruneExpiredRefreshTokens

func (s *Store) PruneExpiredRefreshTokens(now time.Time) bool

PruneExpiredRefreshTokens removes expired refresh tokens.

func (*Store) RevokeAllUserGrants

func (s *Store) RevokeAllUserGrants(userID string, now time.Time) bool

RevokeAllUserGrants revokes all grants and refresh tokens for a user. Returns true if any grants were revoked.

func (*Store) RevokeUserGrant

func (s *Store) RevokeUserGrant(userID, grantID string, now time.Time) bool

RevokeUserGrant revokes one user's grant and all refresh tokens for it.

func (*Store) Save

func (s *Store) Save() error

Save writes the durable OAuth state to its configured path.

Directories

Path Synopsis
data
v5
Package v5 defines version 5 of the persisted OAuth store.
Package v5 defines version 5 of the persisted OAuth store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL