auth

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package auth is wowapi's authentication kernel: it verifies OIDC/JWT bearer tokens against an injectable KeySource (JWKS-over-HTTPS in production, a local signer in tests) and maps validated claims onto an authz.Actor after the app resolves the framework user id and active capacity (D-0037, 01 §3). The Authenticator type adapts a Verifier to the framework's structural httpx.Authenticator so it can be the user leg of a product's composite.

Two properties are structural, not configurable:

  • asymmetric signatures only: the verifier asserts the token's signing method is RSA or ECDSA (RS256/ES256) before touching the key, so "alg":"none" and HMAC tokens are rejected outright (algorithm-confusion defense);
  • opaque failures: every bad/missing/expired/wrong-issuer/wrong-audience/ unknown-kid/bad-signature case returns errors.E(KindUnauthenticated, ...) and never echoes the token or key material. A KeySource transport fault (unreachable JWKS) surfaces as a KindExternal HARD error, not a 401, so a composite authenticator does not mask a transient outage as a clean reject.

Import law (blueprint 04 §1; boundary lint): this package imports only stdlib, kernel/errors, kernel/authz, google/uuid and the jwt library — never module, app, adapters or testkit. The DB-backed user/capacity lookup is injected as the PrincipalStore port, wired by the app.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Authenticator

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

Authenticator adapts a Verifier to the framework's structural httpx.Authenticator: it reads an "Authorization: Bearer <jwt>" header, verifies the token, and maps its claims to an authz.Actor via the PrincipalStore. It is the OIDC/JWT user leg of a product's composite authenticator (roadmap S1/CA-2). It satisfies httpx.Authenticator structurally — kernel/auth never imports kernel/httpx (import law).

Decline vs. fault: a missing bearer token or a non-JWT token (e.g. an API key) yields KindUnauthenticated so a composite falls through to the next scheme; a JWKS/transport fault propagates as a hard error so it is not masked as a 401.

func NewAuthenticator

func NewAuthenticator(v *Verifier, ps PrincipalStore) *Authenticator

NewAuthenticator builds the OIDC/JWT authenticator over a Verifier and the app-supplied PrincipalStore (subject → framework user id + capacity check).

func (*Authenticator) Authenticate

func (a *Authenticator) Authenticate(r *http.Request) (authz.Actor, error)

Authenticate resolves the user actor from the request's bearer JWT. It declines (KindUnauthenticated) when no bearer token is present.

type Claims

type Claims struct {
	jwt.RegisteredClaims
	TenantID           uuid.UUID `json:"tenant_id"`
	CapacityID         uuid.UUID `json:"capacity_id,omitempty"`
	ImpersonatorUserID uuid.UUID `json:"impersonator_user_id,omitempty"`
	BreakGlass         bool      `json:"break_glass,omitempty"`
	// AMR is the standard authentication-methods-references claim (RFC 8176,
	// e.g. ["pwd","mfa"]) surfaced by the IdP. Verifier.Actor propagates it to
	// authz.Actor.AMR, which drives step-up (MFA) enforcement (roadmap S3). A
	// malformed amr in the token (wrong JSON shape) fails the claims decode in
	// Verify, so Actor never sees a token whose amr could not be parsed.
	AMR []string `json:"amr,omitempty"`
}

Claims carries the wowapi-specific token payload alongside the standard registered claims. Subject (sub) maps to a user's idp_subject; TenantID and the optional CapacityID/ImpersonatorUserID/BreakGlass drive the authz.Actor.

func (Claims) Subject

func (c Claims) Subject() string

Subject returns the token subject (sub), which maps to a user's idp_subject.

type Config

type Config struct {
	Issuer   string        // expected iss
	Audience string        // expected aud
	Leeway   time.Duration // clock-skew tolerance (default 30s)
}

Config parameterizes a Verifier.

type JWKSConfig

type JWKSConfig struct {
	// Issuer is the token issuer (the iss claim). When JWKSURI is empty it is
	// used for OIDC discovery at <issuer>/.well-known/openid-configuration.
	Issuer string
	// JWKSURI is the explicit JWKS endpoint. When empty it is discovered from
	// Issuer. It must be https (loopback http is permitted for tests/local IdPs).
	JWKSURI string
	// TTL bounds how long fetched keys are cached before a refetch (default 15m).
	TTL time.Duration
	// Client is the HTTP client used to fetch discovery/JWKS documents. A nil
	// client defaults to one with a 10s timeout.
	Client *http.Client
	// Now is the clock, injectable for tests (default time.Now). It drives the
	// cache TTL and the rotation-refetch throttle.
	Now func() time.Time
}

JWKSConfig parameterizes a JWKS-over-HTTPS KeySource.

type KeySource

type KeySource interface {
	// Key returns the verification key (e.g. *rsa.PublicKey) for the given kid.
	Key(ctx context.Context, kid string) (any, error)
}

KeySource resolves a token-verification key by its key id (kid). Production wires a caching JWKS-over-HTTPS adapter; tests wire a static in-memory source.

func NewJWKSKeySource

func NewJWKSKeySource(cfg JWKSConfig) (KeySource, error)

NewJWKSKeySource builds a caching JWKS-over-HTTPS KeySource. It requires an Issuer (for discovery) or an explicit JWKSURI; a non-loopback URL must be https. Returns an error only for static misconfiguration; network fetches happen lazily on the first Key call.

func NewStaticKeySource

func NewStaticKeySource(keys map[string]any) KeySource

NewStaticKeySource returns a KeySource that resolves kids from an in-memory map. The map is copied so later mutation by the caller cannot affect it.

type PrincipalStore

type PrincipalStore interface {
	// UserIDBySubject returns the framework user id for an IdP subject.
	UserIDBySubject(ctx context.Context, subject string) (uuid.UUID, error)
	// ValidateCapacity returns a non-nil error if capacityID is not an active
	// capacity of userID in tenantID.
	ValidateCapacity(ctx context.Context, userID, tenantID, capacityID uuid.UUID) error
}

PrincipalStore resolves the framework user id from the IdP subject and confirms the capacity belongs to that user in the tenant. Implemented in the app/adapters DB layer (kernel/auth may not import a database).

type Verifier

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

Verifier parses and validates bearer tokens and maps their claims to actors.

func NewVerifier

func NewVerifier(keys KeySource, cfg Config) *Verifier

NewVerifier builds a Verifier over keys and cfg. A zero cfg.Leeway defaults to 30s.

func (*Verifier) Actor

func (v *Verifier) Actor(ctx context.Context, claims Claims, ps PrincipalStore) (authz.Actor, error)

Actor maps validated Claims onto an authz.Actor. It resolves the framework user id from the subject via ps (an unknown subject → KindUnauthenticated) and, when a capacity is present, confirms it belongs to that user in the tenant (a mismatch → KindForbidden). Impersonation and break-glass carry through.

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, tokenString string) (Claims, error)

Verify parses and validates an RS256 bearer token: it asserts the signing method is RSA (rejecting "alg":"none"/HMAC), resolves the key via KeySource by the token's kid header, and checks iss/aud/exp/nbf with the configured leeway. On success it returns the validated Claims; every failure mode returns errors.E(KindUnauthenticated, ...) with no token or key material in the error.

Jump to

Keyboard shortcuts

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