verify

package
v0.99.2 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	AccessTokenType                  = jwtkit.AccessTokenType
	DelegatedAccessTokenType         = jwtkit.DelegatedAccessTokenType
	RemoteApplicationAccessTokenType = jwtkit.RemoteApplicationAccessTokenType
)

Token-type tags used by the verification layer. Sourced from jwtkit so they stay in lockstep with the signer; authhttp exposes the same values via its own delegation.go constants.

View Source
const APIKeyPrincipalType = "api-key"

APIKeyPrincipalType is the TokenType value carried by an opaque API key: a machine credential, not a user.

View Source
const DefaultOutboundTimeout = netguard.DefaultTimeout

DefaultOutboundTimeout bounds the verify layer's outbound HTTP calls (JWKS fetches).

View Source
const DefaultSensitiveMaxAge = 15 * time.Minute
View Source
const MaxDelegatedRoles = 64

MaxDelegatedRoles bounds how many role UUIDs we lift from attributes.roles on a delegated token, so a hostile issuer can't inflate a principal unboundedly.

View Source
const RemoteApplicationTokenType = "remote_application"

RemoteApplicationTokenType is the TokenType value carried by a remote application access token: a remote_application acting AS ITSELF. Like an API-key principal it carries Permissions (its STORED authority) but no UserID; the live-user enrichment/ban gate is skipped (there is no user).

Variables

View Source
var (
	// ErrSenderProofRequired rejects a certificate-bound token presented
	// without its certificate: no TLS peer, a different leaf, or a token-only
	// verification detached from its request.
	ErrSenderProofRequired = authkit.E(authkit.CodeSenderProofRequired)
	// ErrInvalidConfirmation rejects a `cnf` claim that is not exactly
	// {"x5t#S256": <unpadded base64url sha256>}.
	ErrInvalidConfirmation = authkit.E(authkit.CodeInvalidConfirmation)
	// ErrConfirmationWrongTokenType rejects `cnf` on any token type AuthKit does
	// not bind — accepting an unenforced binding would be a silent downgrade.
	ErrConfirmationWrongTokenType = authkit.E(authkit.CodeConfirmationWrongTokenType)
)

RFC 8705 certificate-bound delegated tokens (ak#277). A `cnf.x5t#S256` claim is honoured only against the leaf certificate Go's TLS stack authenticated on THIS request; no header or context value can stand in.

View Source
var ErrLivenessUnconfigured = errors.New("verify: liveness gate used without a LivenessSource (call Verifier.WithLiveness)")

ErrLivenessUnconfigured is returned by VerifyRequestLive when no LivenessSource is wired. It is NOT a wire error: a missing source is a host wiring mistake, not a bad credential, and conflating the two would let a deployment that cannot check liveness look like one where every user is banned. RequiredLive refuses at construction so this can only be reached by an out-of-band caller.

Functions

func Allow added in v0.71.0

func Allow(ctx context.Context, checker PermissionChecker, cl Claims, perm authkit.Perm, scope PermissionScope) (bool, error)

Allow checks machine permission ceilings against the exact UUID and authority issuer. Unbound delegated permissions retain their explicit issuer-trust contract. Human permissions always come from live assignments on GroupID. A missing or mismatched machine binding never falls back to human authority.

func NewSSRFGuardedClient

func NewSSRFGuardedClient() *http.Client

NewSSRFGuardedClient returns a timeout-bounded *http.Client whose dialer resolves the target itself and refuses any private/reserved address, so a crafted jwks_uri (including DNS rebinding) can never reach internal services. WithSSRFGuard installs it on a Verifier.

func Optional

func Optional(v *Verifier) func(http.Handler) http.Handler

Optional validates when Authorization is present; otherwise passes through. Gin hosts: use the gin-native authkitgin.Optional (adapters/gin) instead of hand-wrapping this.

func RequirePermission added in v0.65.0

func RequirePermission(checker PermissionChecker, perm authkit.Perm, resolve func(*http.Request) PermissionScope) func(http.Handler) http.Handler

RequirePermission authorizes the resolved group once and places that exact scope in the request context for the downstream handler. Missing resolution or any permission-check error denies. Unbound delegated authority is scope-free.

func Required

func Required(v *Verifier) func(http.Handler) http.Handler

Required validates the Bearer token (JWT), enforces iss/aud/exp, and stores claims in request context. Gin hosts: use the gin-native authkitgin.Required (adapters/gin) instead of hand-wrapping this.

func RequiredLive added in v0.92.0

func RequiredLive(v *Verifier) (func(http.Handler) http.Handler, error)

RequiredLive is Required with the per-request account-liveness gate: a banned or deleted user is rejected on their NEXT request instead of at token expiry, and the downstream handler reads fresh identity claims.

It returns ErrLivenessUnconfigured when no LivenessSource is wired. A gate that cannot perform its check is a boot-time configuration error, refused before it reaches the route table rather than degraded to a weaker gate that looks like the stronger one.

func Sensitive added in v0.54.0

func Sensitive(options ...SensitiveOptions) func(http.Handler) http.Handler

func SensitiveClaims added in v0.54.0

func SensitiveClaims(cl Claims, options ...SensitiveOptions) bool

func SetClaims

func SetClaims(ctx context.Context, cl Claims) context.Context

func WithPermissionScope added in v0.98.0

func WithPermissionScope(ctx context.Context, scope PermissionScope) context.Context

WithPermissionScope carries an already authorized scope into a trusted host adapter's handler. Call only after Allow/AllowLive succeeds; this does not authorize anything itself.

Types

type Claims

type Claims struct {
	UserID          string
	Email           string
	EmailVerified   bool
	Username        string
	DiscordUsername string
	SessionID       string
	// DeviceKeyID is the AuthKit-issued machine credential that minted this
	// access token. It is present only on device-key tokens.
	DeviceKeyID     string
	Roles           []string
	Entitlements    []string
	AMR             []string
	ACR             string
	AuthTime        time.Time
	TwoFAEnrollment bool
	// MFAEnrolled reports whether the user has a usable second factor enrolled
	// (claim `mfa_enrolled`, stamped at issue from MFAStatus.Satisfied). The
	// Sensitive() gate uses it to require 2FA from users who have it, while never
	// blocking users who don't.
	MFAEnrolled bool
	Issuer      string
	UserTier    string
	JTI         string

	// A delegated access token carries the external delegated subject in
	// DelegatedSubject (claim `delegated_sub`). It never carries `sub` (UserID
	// stays empty), so the local-user gate does not apply.
	DelegatedSubject string

	// Attributes is the `attributes` claim of a delegated access token: the
	// canonical app-specific ESCAPE HATCH (#75). It is an object of issuer-
	// asserted, NAMESPACED, OPAQUE key/values that AuthKit transports but NEVER
	// interprets —
	// the semantics belong to the consuming app. Each value is in one of two
	// modes (see Attribute / AttributeIsReference):
	//   INLINE    — the value carries the full definition, e.g.
	//               {"tier":{"endpoints":[...],"caps":[...]}}.
	//   REFERENCE — the value is a short JSON string key, e.g. {"tier":"tier-1"},
	//               resolved against a definition the remote_application
	//               registered ahead of time (resolve via the attribute-def
	//               registry, or opt-in verify-time hydration).
	// Reserved well-known keys: `tier` (opaque entitlement-tier string, surfaced
	// as UserTier) and `roles` (uuid array, surfaced as DelegatedRoles).
	// `documents` is forbidden here because it is a top-level signed claim.
	// Everything else is free-form per consuming app. Values are kept as raw
	// JSON so the receiver decodes each into its own typed schema; nil when the
	// claim is absent.
	Attributes map[string]json.RawMessage

	// DelegatedRoles are the delegated subject's role UUIDs carried by a
	// delegated access token under `attributes.roles` (a JSON array of UUID strings). They are
	// extracted and validated at verify (malformed entries dropped, count
	// capped) and surfaced on DelegatedPrincipal.Roles. Downstream services use
	// them as e.g. budget-scope keys; authkit treats them as opaque strings.
	// Nil when absent. Distinct from the native-user Roles claim, which a
	// delegated token never carries.
	DelegatedRoles []string

	// Documents is the validated top-level `documents` claim on a delegated
	// token: versioned document type -> canonical content digest. Payload schema
	// and authorization remain application-owned.
	Documents map[string]string

	// ConfirmationCertificateSHA256 is the RFC 8705 `cnf.x5t#S256` binding of a
	// delegated token, already matched against the TLS peer leaf. Nil for an
	// unbound token.
	ConfirmationCertificateSHA256 *[32]byte

	// TokenTyp is the JOSE `typ` header value. "access+jwt" identifies an
	// AuthKit user access token; "delegated-access+jwt" identifies a delegated
	// access token; "remote-application-access+jwt" identifies a remote
	// application access token.
	TokenTyp string

	// TokenType marks the credential class. Empty for ordinary user JWTs;
	// "api-key" for an API-key principal. An API-key principal carries
	// Permissions but no UserID, so the live-user ban/enrichment gate is skipped
	// (there is no user to look up).
	TokenType string

	// Permissions are the app-defined permission strings an API-key principal
	// carries directly — the PBAC grant. Empty for user principals. authkit
	// treats permission strings as opaque.
	Permissions []string

	// RemoteApplicationID / RemoteApplicationSlug identify the remote_application
	// authenticated by a remote application access token. Populated ONLY for
	// RemoteApplicationTokenType claims, resolved server-side from the validated
	// `iss` (never from a self-asserted token claim). The principal's Permissions
	// carry its STORED, assigned authority.
	RemoteApplicationID   string
	RemoteApplicationSlug string
	// RemoteApplicationDomain / Tier / TrustRoot are the application's stored
	// identity facts (#296), resolved server-side like ID: hosts authorize on
	// an unclaimable identity (id, proven domain, root-registered issuer),
	// never on the slug.
	RemoteApplicationDomain    string
	RemoteApplicationTier      string
	RemoteApplicationTrustRoot string

	// Machine authority is resolved live from the receiving AuthKit deployment.
	// Names are presentation only; UUID and authority issuer fence ownership.
	PermissionGroupID              string
	PermissionGroupAuthorityIssuer string
	PermissionGroupPersona         string
	PermissionGroupInstance        string
}

Claims is a typed view of authenticated user information attached by middleware.

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) (Claims, bool)

func GetClaims

func GetClaims(ctx context.Context) (Claims, error)

func (Claims) Attribute

func (c Claims) Attribute(key string) (json.RawMessage, bool)

Attribute returns the raw JSON value of a single delegated-access-token attribute and whether it was present. The value is opaque (#75): the caller decides whether it is an INLINE definition (a JSON object/array) or a REFERENCE (a JSON string key) — see AttributeIsReference / AttributeReference.

func (Claims) AttributeIsReference

func (c Claims) AttributeIsReference(key string) bool

AttributeIsReference reports whether attribute `key` is a REFERENCE (JSON string) rather than an INLINE definition. Convenience over AttributeReference.

func (Claims) AttributeReference

func (c Claims) AttributeReference(key string) (ref string, ok bool)

AttributeReference reports whether attribute `key` is in REFERENCE mode (a JSON string the consumer resolves against the remote_application's registered definition) and returns the reference key. ok is false for INLINE values (objects/arrays/other) or an absent key. This is the ref-vs-inline detector the consumer uses before resolving against the attribute-def registry.

func (Claims) AuthenticatedWithin added in v0.52.0

func (c Claims) AuthenticatedWithin(maxAge time.Duration) bool

func (Claims) BoundToPermissionGroup added in v0.83.0

func (c Claims) BoundToPermissionGroup() bool

BoundToPermissionGroup reports whether these claims carry an owning permission-group binding (#248) — true for machine principals (API keys, remote-application access tokens) whose authority was resolved server-side from a specific group instance; false for user and delegated tokens.

func (Claims) Delegated

func (c Claims) Delegated() (DelegatedPrincipal, bool)

Delegated returns the typed DelegatedPrincipal when the claims are delegated.

func (Claims) DelegatedAccess

func (c Claims) DelegatedAccess() (DelegatedPrincipal, bool)

DelegatedAccess is the canonical accessor for a delegated access token's principal. It returns the typed DelegatedPrincipal and true only when the claims are a delegated access token (see IsDelegatedAccessToken).

func (Claims) DocumentReference added in v0.86.0

func (c Claims) DocumentReference(documentType string) (documents.Reference, bool)

DocumentReference returns one validated typed document reference carried by these claims. It does not fetch or interpret the referenced payload.

func (Claims) HasAMR added in v0.52.0

func (c Claims) HasAMR(method string) bool

func (Claims) HasEntitlement

func (c Claims) HasEntitlement(ent string) bool

func (Claims) HasPermission

func (c Claims) HasPermission(perm authkit.Perm) bool

HasPermission reports whether the claims carry a permission token covering the requested concrete permission.

func (Claims) HasRole

func (c Claims) HasRole(role string) bool

func (Claims) IsDelegatedAccessToken

func (c Claims) IsDelegatedAccessToken() bool

IsDelegatedAccessToken reports whether these claims represent a delegated access token. The canonical signal is the `typ=delegated-access+jwt` JOSE header plus a delegated subject and no local user subject.

func (Claims) IsUser added in v0.72.0

func (c Claims) IsUser() bool

IsUser reports whether these claims represent a native human user.

func (Claims) PermissionGroupAllows added in v0.83.0

func (c Claims) PermissionGroupAllows(scope PermissionScope) bool

PermissionGroupAllows compares immutable ownership. Missing binding fields on a machine principal deny; an old spelling cannot transfer authority.

func (Claims) Principal added in v0.72.0

func (c Claims) Principal() authkit.Principal

Principal returns the small generic-auth shape for host adapters.

func (Claims) PrincipalKind added in v0.72.0

func (c Claims) PrincipalKind() authkit.PrincipalKind

PrincipalKind reports the broad credential class represented by these claims.

type DelegatedPrincipal

type DelegatedPrincipal struct {
	// Issuer is the validated token issuer the receiving service trusts.
	Issuer           string
	DelegatedSubject string
	// Permissions are the resource-defined permission strings the receiving
	// service authorizes against its own catalog. This is the authority source.
	Permissions []string
	// Attributes is the issuer-asserted escape-hatch bag (#75): namespaced,
	// OPAQUE, consumer-interpreted key/values, each INLINE or REFERENCE (see
	// Claims.Attributes / Claims.AttributeReference). Reserved keys: `tier`
	// (-> UserTier) and `roles` (-> Roles); `documents` is forbidden because it
	// is a top-level signed claim. Raw JSON values.
	Attributes map[string]json.RawMessage
	// Documents are exact typed signed-document references carried by the token.
	Documents map[string]string
	// ConfirmationCertificateSHA256 is the verified certificate binding; nil
	// when the token is an unbound bearer.
	ConfirmationCertificateSHA256 *[32]byte
	// JTI is the token identifier (`jti` claim), when present.
	JTI string
	// UserTier is the resolved tier, sourced from `attributes.tier`.
	UserTier string
	// Roles are the actor's role UUID strings, sourced from `attributes.roles`
	// (each validated as a well-formed UUID at verify; malformed entries are
	// dropped, count is capped). Kept as strings so consumers parse to uuid
	// without forcing a uuid dependency on the principal. Nil when absent.
	Roles []string
}

DelegatedPrincipal is the identity carried by a delegated access token: an external actor (DelegatedSubject) whose authority is bounded by the VALIDATED Issuer plus Permissions. The subject does NOT exist as a local user in the validating service — authorization is by issuer trust plus Permissions, not local-user lookup.

func (DelegatedPrincipal) DocumentReference added in v0.86.0

func (p DelegatedPrincipal) DocumentReference(documentType string) (documents.Reference, bool)

type Enricher

type Enricher interface {
	ResolveAPIKeyDetailed(ctx context.Context, keyID, secret string) (authkit.ResolvedAPIKey, error)
	GetRemoteApplication(ctx context.Context, issuer string) (*authkit.RemoteApplication, error)
	ListEnabledRemoteApplications(ctx context.Context) ([]authkit.RemoteApplication, error)
	ResolveRemoteApplicationAuthority(ctx context.Context, appID string) (authkit.RemoteApplicationAuthority, error)
}

Enricher is the optional, DB-backed hook surface the Verifier and middleware use for best-effort enrichment (roles/email/provider username), the live-user ban/deleted gate, opaque API-key resolution, and remote_application + attribute lookups. *authkit.Service satisfies it. The Verifier holds this as an INTERFACE (not *authkit.Service) so the verification layer carries no hard dependency on core's storage stack — a verify-only consumer can leave it nil or supply a lightweight implementation (#110).

type FederationStats added in v0.98.0

type FederationStats struct {
	Snapshot   int       // enabled issuers in the last snapshot
	SnapshotAt time.Time // when it was taken (zero: never)
	Negative   int       // snapshot members whose registration recently failed
	InFlight   int       // issuers currently being registered
}

FederationStats is a point-in-time view of the verifier's remote-application lazy-load state, for diagnostics and tests. Every count is bounded by the number of enabled remote applications, never by request traffic.

type IssuerKey

type IssuerKey struct {
	KID          string
	PublicKeyPEM string
}

IssuerKey is a public key for an issuer, identified by key ID.

type IssuerOptions

type IssuerOptions struct {
	// JWKSURI is the URL to fetch JWKS from. If set, keys are fetched
	// automatically and refreshed when they expire or an unknown kid appears.
	JWKSURI string

	// Keys are pre-provided public keys as PEM. The caller is responsible for
	// refreshing by calling AddIssuer again with updated keys.
	Keys []IssuerKey

	// RawKeys are pre-provided public keys (e.g., from a co-located authkit.Service).
	RawKeys map[string]crypto.PublicKey

	// CacheTTL controls how long fetched JWKS keys are considered fresh.
	// Default: 10 minutes.
	CacheTTL time.Duration

	// MaxStale controls how long stale keys may be used as fallback after
	// a failed JWKS refresh. Default: 1 hour.
	MaxStale time.Duration

	// RemoteApplicationSlug is the receiver-internal remote-application slug
	// registered for this issuer. Tokens do not self-assert this value; it comes
	// only from the trusted issuer registry.
	RemoteApplicationSlug string

	// IsLocal marks this issuer as the host application's own (first-party) token
	// signer, as opposed to a remote_application/federated issuer. It guards the
	// signing-key registry against a non-local registration overwriting the local
	// issuer entry (AK-AUTH-01); it does not change how claims are parsed.
	IsLocal bool
}

IssuerOptions configures how keys are obtained for an issuer. Provide one of JWKSURI, Keys, or RawKeys.

type LivenessSource added in v0.92.0

type LivenessSource interface {
	UserLivenessByIDs(ctx context.Context, ids []string) (map[string]authkit.UserLiveness, error)
}

LivenessSource resolves account liveness — and the identity fields that are fresh as of that same lookup — for verified user principals (#267). authkit.Client satisfies it, embedded or remote, so wiring is `v.WithLiveness(client)`; verify declares the port rather than importing the engine, exactly as it does for PermissionChecker.

type PermissionChecker added in v0.65.0

type PermissionChecker interface {
	CanOnGroup(ctx context.Context, subject authkit.Subject, groupID string, perm authkit.Perm) (bool, error)
}

PermissionChecker checks live authority on an already resolved immutable group. Hosts resolve a name once at their request boundary and reuse its GroupID.

type PermissionScope added in v0.65.0

type PermissionScope struct {
	GroupID         string
	AuthorityIssuer string
	Persona         authkit.Persona
	Instance        string
}

PermissionScope is a trusted request resolution. GroupID and AuthorityIssuer identify ownership; Persona and Instance describe its canonical public name.

func PermissionScopeFromContext added in v0.98.0

func PermissionScopeFromContext(ctx context.Context) (PermissionScope, bool)

PermissionScopeFromContext returns the exact group authorized by middleware, so a domain handler does not resolve the mutable path a second time.

type PermissionValidator

type PermissionValidator func(permissions []string) error

PermissionValidator validates a delegated access token's `permissions` against the receiving service's own permissions. Return an error to reject the token. Called only for delegated access tokens.

type RemoteApplicationSource

type RemoteApplicationSource interface {
	ListEnabledRemoteApplications(ctx context.Context) ([]authkit.RemoteApplication, error)
	// GetRemoteApplication fetches a SINGLE remote_application by its issuer,
	// used after signature verification to resolve a service principal
	// (remoteApplication). The lazy-load-on-miss path never calls it: it answers
	// from the ListEnabledRemoteApplications snapshot (ak#297). *authkit.Service already
	// implements this.
	GetRemoteApplication(ctx context.Context, issuer string) (*authkit.RemoteApplication, error)
}

RemoteApplicationSource is the minimal store contract the Verifier needs to load remote_application principals (#74). *authkit.Service satisfies it. An embedding app may supply its own implementation in tests.

type SensitiveOptions added in v0.54.0

type SensitiveOptions struct {
	MaxAge        time.Duration
	AMR           []string
	ACR           string
	StepUpMethods []string
}

type ServiceJWTVerifyOption

type ServiceJWTVerifyOption func(*serviceJWTVerifyConfig)

ServiceJWTVerifyOption configures VerifyServiceJWT.

func WithServiceJWTMaxLifetime

func WithServiceJWTMaxLifetime(d time.Duration) ServiceJWTVerifyOption

WithServiceJWTMaxLifetime caps accepted service-JWT lifetime. Empty defaults to AuthKit's 15-minute service-JWT lifetime.

type Verifier

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

Verifier validates JWTs from one or more issuers.

For verify-only mode, create with NewVerifier and add issuers via AddIssuer. For issuing mode, authhttp.Service creates a Verifier internally.

func NewVerifier

func NewVerifier(opts ...VerifierOption) *Verifier

NewVerifier creates a new Verifier. Add trusted issuers via AddIssuer.

func (*Verifier) AddIssuer

func (v *Verifier) AddIssuer(issuerID string, audiences []string, opts IssuerOptions) error

AddIssuer registers (or updates) a trusted issuer. This is the single method for adding any issuer — whether at startup or at runtime, whether keys come from a JWKS URL or are pre-provided.

func (*Verifier) AddMFAEnrollmentExemptRoutes added in v0.98.0

func (v *Verifier) AddMFAEnrollmentExemptRoutes(paths []string) *Verifier

AddMFAEnrollmentExemptRoutes registers ANCHORED exempt paths (mount prefix + route path), matched exactly. authhttp.MountHandler calls it with the prefix it mounted under; once any anchored route is registered the suffix match of SetMFAEnrollmentExemptPaths is no longer consulted, so a host route that merely ends in "/user/2fa" cannot be reached with an enrollment-only token (ak#324). The suffix form remains for verify-only consumers that never mount.

func (*Verifier) AllowLive added in v0.92.0

func (v *Verifier) AllowLive(ctx context.Context, checker PermissionChecker, cl Claims, perm authkit.Perm, scope PermissionScope) (bool, error)

AllowLive is Allow with the account-liveness precondition: "this account is live AND holds perm", in one call.

It exists because both consumer hosts had independently written that conjunction by hand, each bolting a liveness lookup in front of verify.Allow — two gates a caller could get out of order, or forget one half of. A banned user who still holds a permission assignment must be denied, and that ordering is now the library's to guarantee, not the host's to remember.

Fail-closed throughout: a liveness error, a dead account, or a Can error all deny (the error is returned; callers must deny on a non-nil error).

func (*Verifier) FederationStats added in v0.98.0

func (v *Verifier) FederationStats() FederationStats

func (*Verifier) HTTPClient

func (v *Verifier) HTTPClient() *http.Client

HTTPClient returns the outbound HTTP client the Verifier uses for JWKS fetches (the WithHTTPClient override, or the default timeout-bounded client).

func (*Verifier) HasLiveness added in v0.92.0

func (v *Verifier) HasLiveness() bool

HasLiveness reports whether a LivenessSource is wired. Hosts that mount a liveness-gated route set conditionally can assert this at boot instead of discovering the gap on the first request.

func (*Verifier) IsLive added in v0.92.0

func (v *Verifier) IsLive(ctx context.Context, cl Claims) (bool, authkit.UserLiveness, error)

IsLive reports whether cl's principal is a live account, and returns the fresh identity fields alongside the verdict. It is the programmatic predicate behind VerifyRequestLive, for gates that already hold verified Claims and are not driving an HTTP pipeline.

Non-user principals (no UserID) are live by definition here — their liveness lives on their own credential — and come back with a zero UserLiveness. Fail-closed: an error, or an id the directory does not return, is false.

func (*Verifier) LoadRemoteApplications

func (v *Verifier) LoadRemoteApplications(ctx context.Context, src RemoteApplicationSource, audiences []string) error

LoadRemoteApplications loads the ACTIVE remote_applications from authkit's OWN store (the remote_applications table) and registers each as a trusted issuer via AddIssuer with its JWKS URL. The Verifier's in-house JWKS fetch/refresh then handles the keys — there is NO external push or sync of keys.

audiences, when non-empty, is applied to every loaded issuer (typically this resource server's own audience). Call this at startup, and re-call (e.g. on a ticker, or after an inbound registration) to pick up store changes. Pass the embedding app's authkit.Service (or any RemoteApplicationSource); if nil, the Service provided via WithService is used.

func (*Verifier) RemoveIssuer

func (v *Verifier) RemoveIssuer(issuerID string)

RemoveIssuer removes a previously added issuer.

func (*Verifier) SetMFAEnrollmentExemptPaths added in v0.79.0

func (v *Verifier) SetMFAEnrollmentExemptPaths(paths []string) *Verifier

SetMFAEnrollmentExemptPaths installs the set of route paths that stay reachable to a request blocked by the requireMFAEnrollment gate or carrying a TwoFAEnrollment-only token (#243): the 2FA enroll/challenge/verify surface. AuthKit's server derives this set from its authoritative route registry (authhttp.RouteSpec.MFAEnrollmentExempt) at construction, so a renamed or added enroll route can't silently drift out of the allowlist. A Verifier that never calls this (verify-only, no WithRequireMFAEnrollment) exempts nothing. Paths are suffix-matched against the incoming request path, since AuthKit routes are prefix-neutral (a host may mount them under any prefix).

func (*Verifier) SetRemoteApplicationSource

func (v *Verifier) SetRemoteApplicationSource(src RemoteApplicationSource)

SetRemoteApplicationSource overrides the federation source consulted by the lazy-load-on-miss path (keyForToken). LoadRemoteApplications is the normal way to set it; this is the explicit seam for tests and advanced wiring.

func (*Verifier) ValidateDocumentIssuer added in v0.86.0

func (v *Verifier) ValidateDocumentIssuer(ctx context.Context, issuer string) error

ValidateDocumentIssuer performs the resolver's pre-network trust check. A registered issuer is accepted; a configured remote-application source gets the same bounded lazy-load-on-first-use behavior as token verification.

func (*Verifier) Verify

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

Verify parses + verifies a token and returns typed Claims. It enforces issuer/audience/expiry with the configured skew, plus authkit's user-token invariant, on top of VerifyClaims. ctx bounds every key lookup the verification needs (JWKS fetch, lazy issuer load, remote-application resolution); a cancelled ctx aborts them. It is detached from any request, so a certificate-bound delegated token (cnf) fails with ErrSenderProofRequired here; verify those through VerifyRequest.

func (*Verifier) VerifyClaims

func (v *Verifier) VerifyClaims(ctx context.Context, tokenStr string) (jwt.MapClaims, error)

VerifyClaims parses and cryptographically verifies a token against the registered issuers and returns its RAW validated claims. It performs the generic, token-type-agnostic checks: JWKS key resolution + signature, issuer must be registered, audience match, and exp/nbf/iat with the configured skew. It does NOT apply authkit's user-token semantics (the sub/delegated_sub invariant) or map into the typed Claims struct.

Use it to verify CUSTOM token types (e.g. a host application's capability tokens) that should reuse authkit's single JWKS engine — registry, caching, rotation, lazy-load — while carrying their own claim shape. The caller registers the token's issuer via AddIssuer and parses the returned MapClaims itself. Verify() is built on top of this for authkit's own user tokens.

func (*Verifier) VerifyDelegatedAccess

func (v *Verifier) VerifyDelegatedAccess(ctx context.Context, tokenStr string) (Claims, DelegatedPrincipal, error)

VerifyDelegatedAccess verifies a token, requires it to be a delegated access token, and runs any configured permission/attributes validators. It returns the typed Claims and the DelegatedPrincipal. Use it on resource servers that only accept delegated access tokens and want catalog/policy enforcement.

func (*Verifier) VerifyDelegatedAccessRequest added in v0.98.0

func (v *Verifier) VerifyDelegatedAccessRequest(r *http.Request) (Claims, DelegatedPrincipal, error)

VerifyDelegatedAccessRequest is VerifyDelegatedAccess bound to the request's bearer token and TLS peer certificate, the only path that accepts a certificate-bound (cnf) delegated token.

func (*Verifier) VerifyDocument added in v0.86.0

func (v *Verifier) VerifyDocument(ctx context.Context, document documents.SignedDocument, expected documents.VerifyOptions) (documents.Envelope, error)

VerifyDocument verifies exact-byte digest, strict envelope metadata, trusted issuer/key resolution, JOSE profile, and signature. It never decodes the application-owned Envelope.Payload.

func (*Verifier) VerifyRequest added in v0.65.0

func (v *Verifier) VerifyRequest(r *http.Request) (Claims, error)

VerifyRequest runs the full Required authentication pipeline — bearer parse, API-key resolution, JWT verify, 2FA gate, and (for delegated principals) the fail-closed issuer gate — and returns the claims WITHOUT writing a response. Embedders that authenticate a request outside the middleware chain call this instead of driving Required against a throwaway ResponseWriter. The native-user path is stateless: it does ZERO DB lookups (#215) — no ban gate, no role/email/provider re-enrichment. Ban/deleted is enforced at token mint (login + refresh); the short access TTL bounds the residual window (#90).

That statelessness is now an explicit OPT-OUT, not the only option (#267): a privileged surface that cannot accept the residual window calls VerifyRequestLive (or mounts RequiredLive) and gets the same pipeline plus a per-request account-liveness gate and fresh identity claims. Choose this one deliberately — for genuinely stateless verifiers, and for read paths where a ≤1-TTL window is acceptable.

func (*Verifier) VerifyRequestLive added in v0.92.0

func (v *Verifier) VerifyRequestLive(r *http.Request) (Claims, error)

VerifyRequestLive is VerifyRequest plus a per-request account-liveness gate: the stateful twin of the deliberately stateless default (#215/#267).

It exists because the stateless path leaves a banned or deleted user holding a syntactically valid token until it expires, and every privileged host surface was hand-rolling the same gate around VerifyRequest to close that window — one of them calling the ADMIN directory per request just to refresh a username and email onto the claims. Both of those are this method's job now.

Behaviour:

  • Everything VerifyRequest enforces (bearer parse, API-key resolution, JWT verify, 2FA gates, the delegated issuer gate) runs first, unchanged.
  • Only NATIVE USER principals are liveness-checked. An API key resolves its secret live on every request already, and a delegated principal is gated on its remote application being enabled; neither carries a UserID, and inventing a lookup for them would be a second gate, not a stronger one.
  • FAIL-CLOSED is the only posture. A lookup error, an id the directory does not return, or a not-Allowed verdict all deny with 401. There is no option to fall back to the stateless answer: a gate that opens when its dependency is down is not a gate.
  • The returned Claims carry the FRESH Username, Email and EmailVerified from that same lookup, overwriting whatever the token minted — including overwriting with empty, which is the honest answer for a user who cleared the field. This is what makes a host's per-request AdminGetUser call deletable. Roles and entitlements are deliberately NOT re-enriched here: they already have live reads of their own (RoleSlugsByUsers, Allow, ListEntitlements) and a second copy would be the duplication this issue is removing, not another one of it.

CACHING CONTRACT: none. Exactly one UserLivenessByIDs call per gated request, no memoization, no negative cache. That is not a regression — the hosts this replaces each did one lookup per request — and it is the only version of the contract that can be stated honestly, because any cache reintroduces exactly the stale-authorization window the gate exists to close. A deployment that decides it wants that trade implements LivenessSource itself and owns the staleness window explicitly, rather than inheriting one from a library default.

Compose with permission checks rather than duplicating them: this answers "is this account live", RequirePermission/Allow answer "may it do this".

func (*Verifier) VerifyServiceJWT

func (v *Verifier) VerifyServiceJWT(ctx context.Context, tokenStr string, opts ...ServiceJWTVerifyOption) (authkit.ServiceJWTClaims, error)

VerifyServiceJWT verifies a first-party OIDC service JWT through the verifier's registered issuer/JWKS store and returns the requested permissions. AuthKit does not grant those permissions; the host must intersect them with server-side grants for the issuer/subject/resource.

func (*Verifier) WithLiveness added in v0.92.0

func (v *Verifier) WithLiveness(src LivenessSource) *Verifier

WithLiveness wires the account-liveness backend used by VerifyRequestLive and the RequiredLive middlewares. Pass the authkit.Client the host already holds.

func (*Verifier) WithService

func (v *Verifier) WithService(svc Enricher) *Verifier

WithService wires the engine as the API-key/remote-application resolution backend and as the default remote-application source for lazy-load-on-miss (see keyForToken). (#215/#220: the former per-request roles/provider-username enrichment is gone — the request path is stateless.) *embedded.Client's underlying service satisfies Enricher.

type VerifierOption

type VerifierOption func(*Verifier)

VerifierOption configures a Verifier.

func WithAPIKeyPrefix

func WithAPIKeyPrefix(prefix string) VerifierOption

WithAPIKeyPrefix sets the host application's API-key brand prefix used to detect opaque shared-secret API keys in the middleware. Empty -> bare "st_".

func WithAlgorithms

func WithAlgorithms(algs ...string) VerifierOption

WithAlgorithms REPLACES the allowed JWS algorithm set; it does not add to it.

The default is ["RS256", "ES256", "ES384", "ES512", "EdDSA"], not ["RS256"]. The breadth is deliberate: federated and remote-application issuers legitimately sign with EC or Ed25519 keys and must verify out of the box. Narrow it only if you control every issuer this Verifier accepts.

The list is a pure allow-list checked in keyForToken, so "none" and the symmetric HS* algorithms are absent from the default and rejected there. A caller who adds them anyway does not open an algorithm-confusion hole: authkit only ever hands the parser an asymmetric public key, which golang-jwt's HMAC and none signing methods refuse as the wrong key type.

Passing an empty list rejects every token (fail closed).

func WithHTTPClient

func WithHTTPClient(c *http.Client) VerifierOption

WithHTTPClient sets the HTTP client used for JWKS fetching.

func WithPermissions

func WithPermissions(fn PermissionValidator) VerifierOption

WithPermissions installs a validator that VerifyDelegatedAccess runs against the token's `permissions`. Use it to ensure every permission string belongs to this resource server's permissions.

func WithRemoteApplicationAudiences added in v0.98.0

func WithRemoteApplicationAudiences(audiences ...string) VerifierOption

WithSkew sets the clock skew tolerance for exp/nbf/iat checks. Default: 60s. WithRemoteApplicationAudiences sets the audiences a lazily-loaded remote application issuer is registered with on the keyForToken miss path when the host never calls LoadRemoteApplications (which overrides it). NewServer passes Config.Token.ExpectedAudiences so both load paths enforce the same audience (ak#324).

func WithRequireMFAEnrollment added in v0.72.0

func WithRequireMFAEnrollment(require bool) VerifierOption

WithRequireMFAEnrollment enables the per-request forced-enrollment gate (#148): when 2FA policy is Required, a native-user request whose token shows the user is not yet enrolled (mfa_enrolled absent) is rejected with 2fa_enrollment_required unless it targets a 2FA enroll/challenge route. This makes Required gate the SESSION — every existing un-enrolled user is challenged on their next request, not just new signups. Set by the AuthKit server from TwoFactor.Mode; verify-only resource servers leave it off.

func WithSSRFGuard

func WithSSRFGuard() VerifierOption

WithSSRFGuard installs NewSSRFGuardedClient as the JWKS client: DNS is resolved first and any private/reserved answer is refused. Use it on Verifiers that fetch JWKS from user-registered (remote_application) issuers.

func WithSkew

func WithSkew(d time.Duration) VerifierOption

Jump to

Keyboard shortcuts

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