Documentation
¶
Index ¶
- func HashSecret(secret string) string
- func RandomHex(byteLen int) string
- func RequestClientID(ctx context.Context) string
- func RequestRemoteIP(ctx context.Context) string
- func RequestUserID(ctx context.Context) string
- func WithUserID(ctx context.Context, userID string) context.Context
- type APIKey
- type Code
- type Config
- type LoginData
- type MapAuthenticator
- type OAuthClient
- type OAuthToken
- type Persistence
- type ProtectedResourceMetadata
- type Server
- func (srv *Server) ClientAllowsGrant(clientID, grantType string) bool
- func (srv *Server) GetClient(clientID string) *OAuthClient
- func (srv *Server) HandleAuthorize() http.HandlerFunc
- func (srv *Server) HandleProtectedResourceMetadata() http.HandlerFunc
- func (srv *Server) HandleRegistration() http.HandlerFunc
- func (srv *Server) HandleServerMetadata() http.HandlerFunc
- func (srv *Server) HandleToken() http.HandlerFunc
- func (srv *Server) ListAPIKeys() []*APIKey
- func (srv *Server) Middleware() func(http.Handler) http.Handler
- func (srv *Server) ReconcileAPIKeys(currentHashes map[string]struct{}) int
- func (srv *Server) ReconcileClients(currentClientIDs map[string]struct{}) int
- func (srv *Server) Register(mux *http.ServeMux)
- func (srv *Server) RegisterAPIKey(rawKey, userID string)
- func (srv *Server) RegisterAPIKeyByHash(hash, userID string)
- func (srv *Server) RegisterPreConfiguredClient(client *OAuthClient) error
- func (srv *Server) RemoveClient(clientID string) bool
- func (srv *Server) RevokeAPIKey(keyHash string)
- func (srv *Server) Stop()
- type ServerMetadata
- type UserAccountChecker
- type UserAuthenticator
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HashSecret ¶
HashSecret returns the hex-encoded SHA-256 hash of a secret string.
func RandomHex ¶
RandomHex generates a cryptographically random hex string of the given byte length.
func RequestClientID ¶
RequestClientID returns the OAuth client ID from the context, or "".
func RequestRemoteIP ¶
RequestRemoteIP returns the client IP from the context, or "".
func RequestUserID ¶
RequestUserID returns the authenticated user ID from the context, or "".
Types ¶
type APIKey ¶
type APIKey struct {
KeyHash string `json:"key_hash"`
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
}
APIKey represents a pre-configured API key for Bearer token authentication. Unlike OAuth tokens, API keys are permanent and only removed by revocation.
type Code ¶
type Code struct {
Code string
ClientID string
RedirectURI string
CodeChallenge string
Resource string
UserID string
Scopes []string
ExpiresAt time.Time
// contains filtered or unexported fields
}
Code represents a pending authorization code. Ephemeral, never persisted. Codes start active and are marked inactive on first use. A second attempt to consume an inactive code is treated as a replay attack (RFC 6819 Section 4.4.1.1).
type Config ¶
type Config struct {
// ServerURL is the public-facing base URL of the server (e.g.
// "https://example.com"). Used for issuer, metadata endpoints,
// and audience validation.
ServerURL string
// Users validates credentials during the authorization code flow.
// Required for /oauth/authorize to work.
Users UserAuthenticator
// Persist is the durable storage backend. Pass nil for in-memory
// only operation (useful in tests and single-process deployments).
Persist Persistence
// Logger receives structured log output. Defaults to slog.Default()
// when nil.
Logger *slog.Logger
// APIKeyPrefix, when non-empty, enables API key authentication in
// the middleware. Bearer tokens starting with this prefix are
// treated as API keys rather than OAuth access tokens.
APIKeyPrefix string
// LoginTitle is the heading shown on the login page.
// Defaults to "Sign In" when empty.
LoginTitle string
// LoginSubtitle is the subtitle shown on the login page.
// Defaults to "Sign in to grant access to your account." when empty.
LoginSubtitle string
// GrantTypes lists the grant types advertised in server metadata.
// Defaults to ["authorization_code", "refresh_token"] when nil.
GrantTypes []string
// TrustedProxyHeader, when non-empty, is the HTTP header used to
// extract the client IP address (e.g. "X-Forwarded-For"). Only set
// this when the server runs behind a trusted reverse proxy.
TrustedProxyHeader string
// LoginTemplate, when non-nil, replaces the built-in login page
// with a custom template. The template receives a LoginData struct.
// When nil, the default built-in login page is used.
LoginTemplate *template.Template
// ClientSecretValidator, when non-nil, replaces the default SHA-256
// constant-time comparison used to validate client secrets. This allows
// callers to use bcrypt or other hashing schemes for stored secrets.
//
// The function receives the raw secret from the incoming request and
// the stored hash from the OAuthClient.SecretHash field. Return true
// if the secret matches the hash.
//
// When nil, the default behavior computes SHA-256(rawSecret) and
// performs a constant-time comparison against storedHash. This is safe
// for high-entropy generated secrets but not suitable for user-chosen
// passwords.
ClientSecretValidator func(rawSecret, storedHash string) bool
}
Config holds all configuration for the OAuth 2.1 authorization server.
type LoginData ¶
type LoginData struct {
CSRFToken string
ClientID string
ClientName string
RedirectURI string
State string
CodeChallenge string
CodeChallengeMethod string
Scope string
Resource string
Error string
Title string
Subtitle string
}
LoginData holds the template data passed to the login page. When providing a custom LoginTemplate via Config, your template receives this struct. All hidden form fields must be included for the OAuth flow to work correctly.
type MapAuthenticator ¶
type MapAuthenticator struct {
// contains filtered or unexported fields
}
MapAuthenticator is a UserAuthenticator backed by a map of usernames to SHA-256 password hashes. Plaintext passwords are hashed during construction and not retained. Comparison uses constant-time comparison. Unknown users are compared against a dummy hash to prevent timing-based user enumeration.
func NewMapAuthenticator ¶
func NewMapAuthenticator(users map[string]string) MapAuthenticator
NewMapAuthenticator creates a MapAuthenticator from a map of usernames to plaintext passwords. The passwords are hashed immediately and the plaintext values are not retained.
func (MapAuthenticator) ValidateCredentials ¶
func (m MapAuthenticator) ValidateCredentials(_ context.Context, username, password string) (string, error)
ValidateCredentials checks the username and password against the map. Returns the username as the userID on success, or ("", nil) on failure.
type OAuthClient ¶
type OAuthClient struct {
ClientID string `json:"client_id"`
ClientName string `json:"client_name,omitempty"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
SecretHash string `json:"secret_hash,omitempty"`
IssuedAt int64 `json:"client_id_issued_at,omitempty"`
UserID string `json:"user_id,omitempty"`
}
OAuthClient represents a dynamically registered OAuth client.
type OAuthToken ¶
type OAuthToken struct {
Token string `json:"token,omitempty"`
TokenHash string `json:"token_hash"`
Kind string `json:"kind,omitempty"`
UserID string `json:"user_id"`
Resource string `json:"resource"`
Scopes []string `json:"scopes,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
RefreshToken string `json:"refresh_token,omitempty"`
RefreshHash string `json:"refresh_hash,omitempty"`
ClientID string `json:"client_id,omitempty"`
}
OAuthToken represents an issued access or refresh token. Kind is "access" or "refresh". Raw token values (Token, RefreshToken) are transient and never persisted to disk. Only their SHA-256 hashes are stored.
type Persistence ¶
type Persistence interface {
SaveOAuthToken(token OAuthToken) error
DeleteOAuthToken(tokenHash string) error
AllOAuthTokens() ([]OAuthToken, error)
SaveOAuthClient(client OAuthClient) error
DeleteOAuthClient(clientID string) error
AllOAuthClients() ([]OAuthClient, error)
SaveAPIKey(hash string, key APIKey) error
DeleteAPIKey(hash string) error
AllAPIKeys() (map[string]APIKey, error)
}
Persistence defines the storage backend for tokens, clients, and API keys. Implementations must be safe for concurrent use. Pass nil to New() for in-memory-only operation (useful in tests).
type ProtectedResourceMetadata ¶
type ProtectedResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
BearerMethodsSupported []string `json:"bearer_methods_supported"`
}
ProtectedResourceMetadata is the RFC 9728 response.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the OAuth 2.1 authorization server. Create one with New() and wire its handler methods into your HTTP mux.
func New ¶
New creates a new Server from the given configuration. Call Stop() when the server is no longer needed to release background resources.
func (*Server) ClientAllowsGrant ¶
ClientAllowsGrant reports whether the given client is permitted to use the specified grant type.
func (*Server) GetClient ¶
func (srv *Server) GetClient(clientID string) *OAuthClient
GetClient returns the registered client with the given ID, or nil.
func (*Server) HandleAuthorize ¶
func (srv *Server) HandleAuthorize() http.HandlerFunc
HandleAuthorize returns a handler for GET+POST /oauth/authorize (authorization code flow with PKCE).
func (*Server) HandleProtectedResourceMetadata ¶
func (srv *Server) HandleProtectedResourceMetadata() http.HandlerFunc
HandleProtectedResourceMetadata returns a handler for GET /.well-known/oauth-protected-resource (RFC 9728).
func (*Server) HandleRegistration ¶
func (srv *Server) HandleRegistration() http.HandlerFunc
HandleRegistration returns a handler for POST /oauth/register (RFC 7591 Dynamic Client Registration).
func (*Server) HandleServerMetadata ¶
func (srv *Server) HandleServerMetadata() http.HandlerFunc
HandleServerMetadata returns a handler for GET /.well-known/oauth-authorization-server (RFC 8414).
func (*Server) HandleToken ¶
func (srv *Server) HandleToken() http.HandlerFunc
HandleToken returns a handler for POST /oauth/token (token exchange and refresh).
func (*Server) ListAPIKeys ¶
ListAPIKeys returns all registered API keys.
func (*Server) Middleware ¶
Middleware returns HTTP middleware that validates Bearer tokens (both OAuth access tokens and API keys) and injects user/client/IP information into the request context. Use RequestUserID(), RequestClientID(), and RequestRemoteIP() to extract the values.
func (*Server) ReconcileAPIKeys ¶
ReconcileAPIKeys removes API keys whose hashes are not in currentHashes. Returns the number of keys removed.
func (*Server) ReconcileClients ¶
ReconcileClients removes dynamically registered clients whose IDs are not in currentClientIDs. Pre-configured clients are never removed. Returns the number of clients removed.
func (*Server) Register ¶
Register wires all OAuth endpoint handlers onto the given mux:
GET /.well-known/oauth-protected-resource GET /.well-known/oauth-authorization-server POST /oauth/register GET /oauth/authorize POST /oauth/authorize POST /oauth/token
func (*Server) RegisterAPIKey ¶
RegisterAPIKey registers a raw API key string and associates it with the given user ID. The key is hashed with SHA-256 before storage.
func (*Server) RegisterAPIKeyByHash ¶
RegisterAPIKeyByHash registers an API key using a pre-computed hash. Use this when the raw key is not available and you already hold the SHA-256 hash (e.g. from HashSecret).
func (*Server) RegisterPreConfiguredClient ¶
func (srv *Server) RegisterPreConfiguredClient(client *OAuthClient) error
RegisterPreConfiguredClient adds a pre-configured OAuth client that survives reconciliation. Use this for server-owned clients that should not be removed by ReconcileClients.
Returns an error if the client requests grant types that the server does not support.
func (*Server) RemoveClient ¶
RemoveClient removes a client by ID. Returns true if the client existed and was removed.
func (*Server) RevokeAPIKey ¶
RevokeAPIKey removes the API key with the given hash.
type ServerMetadata ¶
type ServerMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
}
ServerMetadata is the RFC 8414 response.
type UserAccountChecker ¶
type UserAccountChecker interface {
// IsAccountActive returns true if the user account is enabled.
// Returns (false, nil) for disabled accounts.
// Returns non-nil error only for system failures.
IsAccountActive(ctx context.Context, userID string) (bool, error)
}
UserAccountChecker is an optional interface that, when implemented by the Users value passed to Config, enables per-request user account validation. The middleware calls IsAccountActive after successful token validation to verify the user has not been disabled since the token was issued.
If the Users value does not implement this interface, the middleware skips the check (backward compatible).
type UserAuthenticator ¶
type UserAuthenticator interface {
// ValidateCredentials checks username/password and returns the user ID
// on success. Returns ("", nil) if credentials are invalid.
// Returns non-nil error only for system failures (database down, etc.).
ValidateCredentials(ctx context.Context, username, password string) (userID string, err error)
}
UserAuthenticator validates user credentials during the authorization code flow. Implementations may use bcrypt, SHA-256 comparison, LDAP, database lookup, or any other mechanism.