Help us shape the future of Go! Take the 2026 Go Developer Survey

middleware

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package middleware provides HTTP middleware for authentication and authorization.

Package middleware provides HTTP middleware for SystemForge session management.

Index

Constants

View Source
const (
	// ContextKeyAPIKey is the context key for the validated API key.
	ContextKeyAPIKey contextKey = "api_key"

	// ContextKeyPrincipal is the context key for the authenticated principal.
	ContextKeyPrincipal contextKey = "principal"
)

Variables

View Source
var (
	// ErrDPoPProofRequired is returned when a DPoP-bound access token
	// (one with a cnf.jkt claim) is presented without a valid DPoP proof,
	// including as a plain Bearer token.
	ErrDPoPProofRequired = errors.New("DPoP-bound token requires the DPoP scheme and a DPoP proof")
	// ErrDPoPTokenNotBound is returned when a token without a cnf.jkt claim
	// is presented with the DPoP scheme.
	ErrDPoPTokenNotBound = errors.New("token presented with the DPoP scheme is not DPoP-bound")
	// ErrDPoPProofReplayed is returned for a proof whose jti was already
	// used.
	ErrDPoPProofReplayed = errors.New("DPoP proof replayed")
	// ErrBearerTokenRefused is returned for an unbound token (no cnf.jkt)
	// when DPoPConfig.AllowBearer is not set.
	ErrBearerTokenRefused = errors.New("access token is not DPoP-bound; bearer tokens are not accepted")
	// ErrMissingAuthorization is returned for a request without an access
	// token in the Bearer or DPoP scheme.
	ErrMissingAuthorization = errors.New("missing authorization header")
	// ErrReplayCacheRequired is returned by NewDPoPAuth without a replay
	// cache.
	ErrReplayCacheRequired = errors.New("DPoPConfig.ReplayCache is required")
)
View Source
var ErrAPIKeyOwnerNotAllowed = errors.New("API key owner no longer has access")

ErrAPIKeyOwnerNotAllowed is returned by an APIKeyOwnerCheck when a valid key's owner may no longer use it: the owner is deactivated or deleted, or is no longer a member of the key's organization.

Functions

func APIKeyMiddleware

func APIKeyMiddleware(config APIKeyMiddlewareConfig) func(http.Handler) http.Handler

APIKeyMiddleware creates middleware that validates API keys and checks, with config.OwnerCheck, that each key's owner still has access. It panics when config.Service or config.OwnerCheck is nil.

func ChiAuth

func ChiAuth(jwtService *jwt.Service, cfg DPoPConfig) (func(http.Handler) http.Handler, error)

ChiAuth returns a Chi-compatible middleware that validates DPoP-bound access tokens. This is an alias for HTTPAuth since Chi uses the standard http.Handler interface.

Usage with Chi:

auth, err := middleware.ChiAuth(jwtService, middleware.DPoPConfig{ReplayCache: cache})
if err != nil { ... }
r := chi.NewRouter()
r.Use(auth)
r.Get("/api/protected", protectedHandler)

func ChiAuthOptional

func ChiAuthOptional(jwtService *jwt.Service, cfg DPoPConfig) (func(http.Handler) http.Handler, error)

ChiAuthOptional returns a Chi-compatible middleware that validates DPoP-bound access tokens if present. This is an alias for HTTPAuthOptional since Chi uses the standard http.Handler interface.

func ChiRequireAnyPermission

func ChiRequireAnyPermission(permissions ...string) func(http.Handler) http.Handler

ChiRequireAnyPermission returns a Chi-compatible middleware that requires any of the specified permissions.

func ChiRequireAnyRole

func ChiRequireAnyRole(roles ...string) func(http.Handler) http.Handler

ChiRequireAnyRole returns a Chi-compatible middleware that requires any of the specified roles.

func ChiRequireOrganization

func ChiRequireOrganization() func(http.Handler) http.Handler

ChiRequireOrganization returns a Chi-compatible middleware that requires organization context.

func ChiRequirePermission

func ChiRequirePermission(permission string) func(http.Handler) http.Handler

ChiRequirePermission returns a Chi-compatible middleware that requires a specific permission.

func ChiRequirePlatformAdmin

func ChiRequirePlatformAdmin() func(http.Handler) http.Handler

ChiRequirePlatformAdmin returns a Chi-compatible middleware that requires platform admin status.

func ChiRequireRole

func ChiRequireRole(role string) func(http.Handler) http.Handler

ChiRequireRole returns a Chi-compatible middleware that requires a specific role.

Usage with Chi:

r.Group(func(r chi.Router) {
    r.Use(auth) // from middleware.ChiAuth
    r.Use(middleware.ChiRequireRole("admin"))
    r.Get("/api/admin", adminHandler)
})

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) *jwt.Claims

ClaimsFromContext extracts JWT claims from the context. Returns nil if no claims are present.

func ContextWithClaims

func ContextWithClaims(ctx context.Context, claims *jwt.Claims) context.Context

ContextWithClaims returns a new context with the JWT claims attached.

func GetAPIKey

func GetAPIKey(ctx context.Context) *apikey.APIKey

GetAPIKey retrieves the validated API key from the request context.

func HTTPAuth

func HTTPAuth(jwtService *jwt.Service, cfg DPoPConfig) (func(http.Handler) http.Handler, error)

HTTPAuth returns middleware that validates access tokens and attaches their claims to the request context. It requires DPoP (RFC 9449): a token must be bound to a key (cnf.jkt) and come with the "DPoP" scheme and a valid proof by that key; see NewDPoPAuth. Unbound bearer tokens are refused unless cfg.AllowBearer is set. Only access tokens addressed to the service's Config.Audience are accepted.

func HTTPAuthBearer added in v0.21.0

func HTTPAuthBearer(jwtService *jwt.Service) func(http.Handler) http.Handler

HTTPAuthBearer returns middleware that validates unbound bearer JWTs: it extracts the token from the Authorization header (Bearer scheme) and attaches the claims to the request context. Only access tokens addressed to the service's Config.Audience are accepted, and a DPoP-bound token is refused. Prefer HTTPAuth, which requires DPoP.

func HTTPAuthBearerForAudience added in v0.21.0

func HTTPAuthBearerForAudience(jwtService *jwt.Service, audiences ...string) func(http.Handler) http.Handler

HTTPAuthBearerForAudience is HTTPAuthBearer for routes that serve one kind of client: it admits only access tokens addressed to one of the audiences (for example jwt.AudienceBFF on browser routes), so a token minted for another audience, such as a narrowly scoped API token, cannot be used as a full session. Prefer HTTPAuthForAudience, which requires DPoP.

func HTTPAuthBearerOptional added in v0.21.0

func HTTPAuthBearerOptional(jwtService *jwt.Service) func(http.Handler) http.Handler

HTTPAuthBearerOptional is HTTPAuthBearer that lets requests without a valid token through without claims. Prefer HTTPAuthOptional, which requires DPoP.

func HTTPAuthBearerOptionalWithObservability added in v0.21.0

func HTTPAuthBearerOptionalWithObservability(jwtService *jwt.Service, obs *observability.Observability) func(http.Handler) http.Handler

HTTPAuthBearerOptionalWithObservability is HTTPAuthBearerOptional with observability. If obs is nil, metrics are not recorded.

func HTTPAuthBearerWithObservability added in v0.21.0

func HTTPAuthBearerWithObservability(jwtService *jwt.Service, obs *observability.Observability) func(http.Handler) http.Handler

HTTPAuthBearerWithObservability is HTTPAuthBearer with observability. If obs is nil, metrics are not recorded.

func HTTPAuthForAudience added in v0.14.0

func HTTPAuthForAudience(jwtService *jwt.Service, cfg DPoPConfig, audiences ...string) (func(http.Handler) http.Handler, error)

HTTPAuthForAudience is HTTPAuth for routes that serve one kind of client: it admits only access tokens addressed to one of the audiences (for example jwt.AudienceBFF on browser routes), so a token minted for another audience, such as a narrowly scoped API token, cannot be used as a full session.

func HTTPAuthOptional

func HTTPAuthOptional(jwtService *jwt.Service, cfg DPoPConfig) (func(http.Handler) http.Handler, error)

HTTPAuthOptional is HTTPAuth that lets requests without valid credentials through without claims.

func HasAllPermissions

func HasAllPermissions(ctx context.Context, permissions ...string) bool

HasAllPermissions checks if the user has all of the specified permissions.

func HasAnyPermission

func HasAnyPermission(ctx context.Context, permissions ...string) bool

HasAnyPermission checks if the user has any of the specified permissions.

func HasAnyRole

func HasAnyRole(ctx context.Context, roles ...string) bool

HasAnyRole checks if the user has any of the specified roles.

func HasPermission

func HasPermission(ctx context.Context, permission string) bool

HasPermission checks if the user has a specific permission.

func HasRole

func HasRole(ctx context.Context, role string) bool

HasRole checks if the user has a specific role.

func IsPlatformAdminFromContext

func IsPlatformAdminFromContext(ctx context.Context) bool

IsPlatformAdminFromContext checks if the user is a platform admin. Returns false if no claims are present.

func OptionalAPIKey

func OptionalAPIKey(service *apikey.Service, ownerCheck APIKeyOwnerCheck) func(http.Handler) http.Handler

OptionalAPIKey creates middleware that validates API keys if present but doesn't require them. A key that is invalid, or whose owner fails ownerCheck, leaves the request unauthenticated. It panics when service or ownerCheck is nil.

func OrganizationIDFromContext

func OrganizationIDFromContext(ctx context.Context) *uuid.UUID

OrganizationIDFromContext extracts the organization ID from the context. Returns nil if no organization context is present.

func PermissionsFromContext

func PermissionsFromContext(ctx context.Context) []string

PermissionsFromContext extracts the user's permissions from the context. Returns nil if no permissions are present.

func PrincipalIDFromContext

func PrincipalIDFromContext(ctx context.Context) uuid.UUID

PrincipalIDFromContext extracts the principal ID from the context. Returns uuid.Nil if no claims or principal ID is present.

func PrincipalTypeFromContext

func PrincipalTypeFromContext(ctx context.Context) string

PrincipalTypeFromContext extracts the principal type from the context. Returns empty string if no claims are present.

func RequireAPIKey

func RequireAPIKey(service *apikey.Service, ownerCheck APIKeyOwnerCheck) func(http.Handler) http.Handler

RequireAPIKey creates middleware that requires a valid API key whose owner passes ownerCheck. This is a convenience function with default configuration.

func RequireAPIKeyWithScopes

func RequireAPIKeyWithScopes(service *apikey.Service, ownerCheck APIKeyOwnerCheck, scopes ...string) func(http.Handler) http.Handler

RequireAPIKeyWithScopes creates middleware that requires specific scopes.

func RequireAnyPermission

func RequireAnyPermission(permissions ...string) func(http.Handler) http.Handler

RequireAnyPermission returns middleware that requires any of the specified permissions. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequireAnyRole

func RequireAnyRole(roles ...string) func(http.Handler) http.Handler

RequireAnyRole returns middleware that requires the user to have any of the specified roles. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequireAnyScope

func RequireAnyScope(scopes ...string) func(http.Handler) http.Handler

RequireAnyScope creates middleware that checks for any of the given scopes.

func RequireOrganization

func RequireOrganization() func(http.Handler) http.Handler

RequireOrganization returns middleware that requires an organization context. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequirePermission

func RequirePermission(permission string) func(http.Handler) http.Handler

RequirePermission returns middleware that requires a specific permission. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequirePlatformAdmin

func RequirePlatformAdmin() func(http.Handler) http.Handler

RequirePlatformAdmin returns middleware that requires platform admin status. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequireRole

func RequireRole(role string) func(http.Handler) http.Handler

RequireRole returns middleware that requires the user to have a specific role. Must be used after HTTPAuth (or HTTPAuthBearer) middleware.

func RequireScope

func RequireScope(scope string) func(http.Handler) http.Handler

RequireScope creates middleware that checks for a specific scope. This should be used after APIKeyMiddleware.

func RoleFromContext

func RoleFromContext(ctx context.Context) string

RoleFromContext extracts the user's role from the context. Returns empty string if no role is present.

func UserIDFromContext

func UserIDFromContext(ctx context.Context) uuid.UUID

UserIDFromContext extracts the principal ID from the context. Deprecated: Use PrincipalIDFromContext instead.

Types

type APIKeyMiddlewareConfig

type APIKeyMiddlewareConfig struct {
	// Service is the API key service.
	Service *apikey.Service

	// OwnerCheck confirms that the key's owner still has access
	// (required). APIKeyMiddleware panics without it.
	OwnerCheck APIKeyOwnerCheck

	// RequiredScopes are scopes that must be present (all required).
	RequiredScopes []string

	// AnyScopes requires at least one of these scopes.
	AnyScopes []string

	// HeaderName is the header containing the API key.
	// Default: "Authorization" with "Bearer" scheme.
	HeaderName string

	// AllowQueryParam enables API key in query parameter.
	// Default: false (more secure).
	AllowQueryParam bool

	// QueryParamName is the query parameter name.
	// Default: "api_key".
	QueryParamName string

	// RecordUsage updates the last used timestamp on each request.
	// Default: true.
	RecordUsage bool

	// OnError is called when authentication fails.
	// If nil, returns 401 Unauthorized.
	OnError func(w http.ResponseWriter, r *http.Request, err error)

	// OnSuccess is called after successful authentication.
	OnSuccess func(r *http.Request, key *apikey.APIKey)

	// IPExtractor extracts the client IP from the request.
	// If nil, uses r.RemoteAddr.
	IPExtractor func(r *http.Request) string

	// Observability is the observability provider for metrics.
	// If nil, no metrics are recorded.
	Observability *observability.Observability
}

APIKeyMiddlewareConfig contains configuration for the API key middleware.

func DefaultAPIKeyMiddlewareConfig

func DefaultAPIKeyMiddlewareConfig() APIKeyMiddlewareConfig

DefaultAPIKeyMiddlewareConfig returns default configuration.

type APIKeyOwnerCheck added in v0.21.0

type APIKeyOwnerCheck func(ctx context.Context, key *apikey.APIKey) error

APIKeyOwnerCheck confirms, on every request, that the owner of a valid key may still use it. It must check that key.OwnerID is an active principal and, when key.OrganizationID is set, that the owner is still an active member of that organization, and return ErrAPIKeyOwnerNotAllowed (possibly wrapped) when not. Any error refuses the request.

A key's validity only proves who created it and what it was scoped to then; without this check, a key keeps asserting its organization after its owner was removed from it or deactivated.

type DPoPAuth added in v0.21.0

type DPoPAuth struct {
	// contains filtered or unexported fields
}

DPoPAuth validates access tokens and enforces DPoP sender constraint.

func NewDPoPAuth added in v0.21.0

func NewDPoPAuth(jwtService *jwt.Service, cfg DPoPConfig) (*DPoPAuth, error)

NewDPoPAuth returns DPoP-aware token authentication. A token bound to a DPoP key (cnf.jkt) is accepted only with the "DPoP" authorization scheme and a proof that is signed by that key, matches the request method and URI, carries the hash of the token (ath), is fresh, and has not been used before. A token without cnf.jkt is refused unless DPoPConfig.AllowBearer is set, and then accepted only with the "Bearer" scheme.

func (*DPoPAuth) Authenticate added in v0.21.0

func (a *DPoPAuth) Authenticate(r *http.Request, audiences ...string) (*jwt.Claims, error)

Authenticate validates the request's access token and, for a DPoP-bound token, its DPoP proof, and returns the token's claims.

func (*DPoPAuth) Middleware added in v0.21.0

func (a *DPoPAuth) Middleware(audiences ...string) func(http.Handler) http.Handler

Middleware returns middleware that authenticates the request and attaches the token's claims to the context, accepting only tokens addressed to one of audiences (all of the service's Config.Audience when none are given).

func (*DPoPAuth) OptionalMiddleware added in v0.21.0

func (a *DPoPAuth) OptionalMiddleware(audiences ...string) func(http.Handler) http.Handler

OptionalMiddleware is Middleware that lets a request without valid credentials through without claims, for routes that serve anonymous and signed-in users alike.

type DPoPConfig added in v0.21.0

type DPoPConfig struct {
	// Verification sets the proof age window and clock skew. Access token
	// binding (the ath claim) is always required. Zero values use
	// dpop.DefaultVerificationConfig.
	Verification dpop.VerificationConfig

	// ReplayCache rejects a proof whose jti has been used before.
	// Required: use NewMemoryReplayCache for a single instance and
	// NewRedisReplayCache when several instances serve the API.
	ReplayCache ReplayCache

	// BaseURL is the scheme and host clients use to reach this API, such as
	// "https://api.example.com"; the proof's htu must equal BaseURL plus the
	// request path. If empty, the scheme comes from the connection (TLS or
	// not) and the host from the Host header. Forwarded headers are never
	// trusted, so set BaseURL behind a proxy that terminates TLS.
	BaseURL string

	// AllowBearer also accepts unbound tokens (without cnf.jkt) with the
	// Bearer scheme. It is an explicit opt-in for legacy clients: by
	// default an unbound token is refused, since whoever holds a bearer
	// token can use it. A DPoP-bound token is never accepted as Bearer.
	AllowBearer bool

	// Observability, when set, records token validation metrics.
	Observability *observability.Observability
}

DPoPConfig configures resource-side DPoP verification (RFC 9449).

type ErrorResponse

type ErrorResponse struct {
	Error   string `json:"error"`
	Message string `json:"message,omitempty"`
}

ErrorResponse represents an error response body.

type MemoryReplayCache added in v0.21.0

type MemoryReplayCache struct {
	// contains filtered or unexported fields
}

MemoryReplayCache is an in-process ReplayCache, suitable when one instance serves the API.

func NewMemoryReplayCache added in v0.21.0

func NewMemoryReplayCache() *MemoryReplayCache

NewMemoryReplayCache returns an empty in-process replay cache.

func (*MemoryReplayCache) Use added in v0.21.0

func (c *MemoryReplayCache) Use(_ context.Context, key string, expiresAt time.Time) (bool, error)

Use implements ReplayCache.

type Principal

type Principal struct {
	// Type is "user" or "api_key".
	Type string `json:"type"`

	// ID is the principal's unique identifier.
	ID uuid.UUID `json:"id"`

	// UserID is the user's ID (same as ID for users, owner ID for API keys).
	UserID uuid.UUID `json:"user_id"`

	// OrganizationID is the organization context (optional).
	OrganizationID *uuid.UUID `json:"organization_id,omitempty"`

	// Scopes are the permissions granted to this principal.
	Scopes []string `json:"scopes,omitempty"`

	// Environment is "live" or "test" for API keys.
	Environment string `json:"environment,omitempty"`

	// Metadata contains additional principal data.
	Metadata map[string]string `json:"metadata,omitempty"`
}

Principal represents an authenticated entity (user or API key).

func GetPrincipal

func GetPrincipal(ctx context.Context) *Principal

GetPrincipal retrieves the authenticated principal from the request context.

func (*Principal) HasScope

func (p *Principal) HasScope(scope string) bool

HasScope returns true if the principal has the given scope.

func (*Principal) IsAPIKey

func (p *Principal) IsAPIKey() bool

IsAPIKey returns true if the principal is an API key.

func (*Principal) IsUser

func (p *Principal) IsUser() bool

IsUser returns true if the principal is a user.

type RedisReplayCache added in v0.21.0

type RedisReplayCache struct {
	// contains filtered or unexported fields
}

RedisReplayCache is a ReplayCache shared through Redis, for APIs served by several instances.

func NewRedisReplayCache added in v0.21.0

func NewRedisReplayCache(client redis.UniversalClient, prefix string) *RedisReplayCache

NewRedisReplayCache returns a replay cache that stores keys in Redis under prefix (for example "dpop-jti:").

func (*RedisReplayCache) Use added in v0.21.0

func (c *RedisReplayCache) Use(ctx context.Context, key string, expiresAt time.Time) (bool, error)

Use implements ReplayCache with SET NX and an expiry.

type ReplayCache added in v0.21.0

type ReplayCache interface {
	// Use records key until expiresAt and reports whether it was already
	// recorded (a replay).
	Use(ctx context.Context, key string, expiresAt time.Time) (replayed bool, err error)
}

ReplayCache records DPoP proof identifiers so that each proof is accepted once. Implementations must be safe for concurrent use and, when several instances serve the same API, shared between them.

Jump to

Keyboard shortcuts

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