authhttp

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: 40 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ActionUpdateUsername       = authkit.ActionUpdateUsername
	ActionRequestPasswordReset = authkit.ActionRequestPasswordReset
	ActionRequestVerification  = authkit.ActionRequestVerification
)
View Source
const (
	// 2FA-specific rate limit buckets
	RL2FAStartPhone      = "auth_2fa_start_phone"
	RL2FAStartTOTP       = "auth_2fa_start_totp"
	RL2FAEnable          = "auth_2fa_enable"
	RL2FADisable         = "auth_2fa_disable"
	RL2FARegenerateCodes = "auth_2fa_regenerate_codes"
	RL2FAVerify          = "auth_2fa_verify"

	RLAuthToken                = "auth_token"
	RLAuthRegister             = "auth_register"
	RLAuthRegisterAvailability = "auth_register_availability"
	RLAuthRegisterAbandon      = "auth_register_abandon"
	RLInviteCreate             = "auth_invite_create"
	RLPasswordLogin            = "auth_password_login"
	RLPasswordlessStart        = "auth_passwordless_start"
	RLPasswordlessConfirm      = "auth_passwordless_confirm"
	RLPasskeyRegister          = "auth_passkey_register"
	RLPasskeyLogin             = "auth_passkey_login"
	RLDeviceKeyEnrollBegin     = "auth_device_key_enroll_begin"
	RLDeviceKeyEnrollFinish    = "auth_device_key_enroll_finish"
	RLDeviceKeyLoginBegin      = "auth_device_key_login_begin"
	RLDeviceKeyLoginFinish     = "auth_device_key_login_finish"
	RLDeviceKeysManage         = "auth_device_keys_manage"
	RLAuthLogout               = "auth_logout"
	RLAuthSessionsList         = "auth_sessions_list"
	RLAuthSessionsRevoke       = "auth_sessions_revoke"
	RLAuthSessionsRevokeAll    = "auth_sessions_revoke_all"

	// #264 application self-registration (per-IP AND per-domain/slug keys).
	RLApplicationRegister = "application_register"
	// #264 anti-squat velocity: group settings (slug rename IS a claim),
	// keyed per-IP and per-user.
	RLGroupSettings = "group_settings"
	// #263 anti-squat velocity: generated persona-instance creation (a create
	// IS a claim), keyed per-IP and per-user.
	RLGroupCreate = "group_create"

	// #261 delegated-token mint (authenticated; bounds signing cost per IP).
	RLDelegatedTokenMint = "delegated_token_mint"

	RLPasswordResetRequest = "auth_pwd_reset_request"
	RLPasswordResetConfirm = "auth_pwd_reset_confirm"
	// #312: one bucket per contact flow, whichever channel the identifier names.
	RLVerifyRequest        = "auth_verify_request"
	RLVerifyConfirm        = "auth_verify_confirm"
	RLRegisterResend       = "auth_register_resend"
	RLContactChangeRequest = "auth_contact_change_request"

	RLOIDCStart    = "auth_oidc_start"
	RLOIDCCallback = "auth_oidc_callback"

	RLUserPasswordChange    = "auth_user_password_change"
	RLUserMe                = "auth_user_me"
	RLUserUpdateUsername    = "auth_user_update_username"
	RLUserPreferredLanguage = "auth_user_preferred_language"

	RLUserDelete         = "auth_user_delete"
	RLUserUnlinkProvider = "auth_user_unlink_provider"

	RLAdminUserSessionsList = "auth_admin_user_sessions_list"
	// The admin session route revokes ALL of a user's sessions; there is no
	// single-session admin revoke, so no RLAdminUserSessionsRevoke bucket.
	RLAdminUserSessionsRevokeAll = "auth_admin_user_sessions_revoke_all"

	// Solana SIWS authentication
	RLSolanaChallenge = "auth_solana_challenge"
	RLSolanaLogin     = "auth_solana_login"
	RLSolanaLink      = "auth_solana_link"
)

Bucket names used by authkit endpoints.

View Source
const (
	DefaultAPIPrefix = "/api/v1"
	DefaultOIDCPath  = "/oidc"
	JWKSPath         = "/.well-known/jwks.json"
	// DocumentsPath is the root-anchored published-document surface (#260).
	// Addressable in ExcludeRoutes as GET DocumentsPath (dropping GET+HEAD).
	DocumentsPath = "/.well-known/authkit/documents/{digest}"
)

Mount anchors. JWKS and browser OIDC are root-anchored by spec/convention (verifiers derive the JWKS URL from the issuer; OIDC redirect URIs are registered with providers), while the JSON API is prefix-anchored. The resolution (#250): MountHandler is ONE handler mounted at the HOST ROOT — JWKS at JWKSPath, browser OIDC under DefaultOIDCPath, API under APIPrefix.

View Source
const AccessTokenType = jwtkit.AccessTokenType

AccessTokenType is the canonical JOSE `typ` header value for an AuthKit user access token.

View Source
const DelegatedAccessTokenType = jwtkit.DelegatedAccessTokenType

DelegatedAccessTokenType is the canonical JOSE `typ` header value for a delegated access token.

View Source
const RefreshCookieName = "authkit_rt"

RefreshCookieName is the cookie the refresh token rides in.

View Source
const RemoteApplicationAccessTokenType = jwtkit.RemoteApplicationAccessTokenType

RemoteApplicationAccessTokenType is the JOSE `typ` for a remote application access token. AuthKit resolves authority from the stored remote_application assignment, never from role claims in the token.

Variables

This section is empty.

Functions

func DefaultRateLimits

func DefaultRateLimits() map[string]ratelimit.Limit

DefaultRateLimits returns AuthKit's built-in per-endpoint rate limits.

These limits are enforced per client IP (as determined by the Service's ClientIPFunc). Hosts can override by supplying their own limiter via WithRateLimiter(...).

func JWKSHandler

func JWKSHandler(jwks jwtkit.JWKS) http.Handler

JWKSHandler serves the public JWKS document for the given key set.

func LanguageMiddleware

func LanguageMiddleware(cfg *LanguageConfig) func(http.Handler) http.Handler

LanguageMiddleware infers request language and attaches it to the request context.

func MountHandler added in v0.84.0

func MountHandler(svc *Service, opts MountOptions) (h http.Handler, err error)

MountHandler returns the full AuthKit surface — JSON API, browser OIDC, and JWKS — as ONE framework-neutral net/http handler. The host mounts it once (a gin host uses gin.WrapH) and rewrites nothing. Every route keeps the gate its RouteSpec carries; the mount adds no auth and removes none.

Types

type ActionAvailability

type ActionAvailability = authkit.ActionAvailability

type AuthCapabilities

type AuthCapabilities struct {
	Registration AuthRegistrationCapabilities `json:"registration"`
	Providers    []AuthProviderSummary        `json:"providers"`
	Password     AuthPasswordCapabilities     `json:"password"`
	Passwordless AuthPasswordlessCapabilities `json:"passwordless"`
	Passkeys     AuthPasskeyCapabilities      `json:"passkeys"`
	Solana       AuthSolanaCapabilities       `json:"solana"`
	Verification AuthVerificationCapabilities `json:"verification"`
	Languages    []string                     `json:"languages,omitempty"`
}

AuthCapabilities is the public, static auth feature-discovery response.

type AuthPasskeyCapabilities

type AuthPasskeyCapabilities struct {
	Login bool `json:"login"`
}

type AuthPasswordCapabilities

type AuthPasswordCapabilities struct {
	Login bool `json:"login"`
}

type AuthPasswordlessCapabilities

type AuthPasswordlessCapabilities struct {
	Enabled  bool     `json:"enabled"`
	Channels []string `json:"channels,omitempty"`
}

type AuthProviderSummary

type AuthProviderSummary struct {
	ID                   string `json:"id"`
	Name                 string `json:"name"`
	SupportsLogin        bool   `json:"supports_login"`
	SupportsRegistration bool   `json:"supports_registration"`
	SupportsLink         bool   `json:"supports_link"`
}

type AuthRegistrationCapabilities

type AuthRegistrationCapabilities struct {
	Mode                string `json:"mode"`
	InviteTokenRequired bool   `json:"invite_token_required"`
}

type AuthSolanaCapabilities

type AuthSolanaCapabilities struct {
	Login bool `json:"login"`
}

type AuthVerificationCapabilities

type AuthVerificationCapabilities struct {
	Registration string `json:"registration"`
}

type ClientIPFunc

type ClientIPFunc func(r *http.Request) string

ClientIPFunc determines the client IP used for rate limiting and auditing.

Returning an empty string means "unknown" and causes rate limiting to fail open.

func ClientIPFromForwardedHeaders

func ClientIPFromForwardedHeaders(trusted, cloudflare []netip.Prefix) ClientIPFunc

ClientIPFromForwardedHeaders derives the client IP behind proxies the host declared. A peer inside trusted or cloudflare enables the right-to-left X-Forwarded-For walk (hops in either set are skipped as our own). Only a peer inside cloudflare may additionally be trusted for CF-Connecting-IP, and only as a fallback when X-Forwarded-For yields nothing: a generic reverse proxy forwards CF-Connecting-IP verbatim, so honouring it from any trusted peer let a client pick its own rate-limit key (ak#298). Any other peer resolves to itself.

Hosts that pass a cloudflare set must also lock the origin down to Cloudflare ingress; otherwise a client that reaches the origin directly is its own peer and both headers are ignored, which is the safe outcome.

func DefaultClientIP

func DefaultClientIP() ClientIPFunc

DefaultClientIP returns the immediate peer IP from RemoteAddr.

This intentionally includes private and loopback peers so embedded/local deployments still get default rate-limit protection. Hosts behind reverse proxies should use ClientIPFromForwardedHeaders with trusted proxy CIDRs when they need the original public client IP instead of the proxy peer.

type Config added in v0.98.0

type Config struct {
	// Redis overrides the engine's Redis client for the HTTP layer's OIDC/SIWS
	// state caches and rate limiter. Nil reuses embedded.Deps.Redis (#210), so
	// most hosts never set it.
	Redis *redis.Client

	// RateLimits overlays bucket-specific limits onto DefaultRateLimits (#242).
	RateLimits map[string]ratelimit.Limit
	// Limiter replaces AuthKit's automatic limiter. ADVANCED: normal
	// deployments let AuthKit own the policy (Redis-backed when Redis is wired,
	// in-memory otherwise). RateLimits are not applied to a custom limiter.
	Limiter RateLimiter
	// DisableRateLimiting turns rate limiting off. TESTS ONLY: it removes the
	// brute-force and spam protection.
	DisableRateLimiting bool

	// Client-IP posture (ak#299). Exactly what sits in front of AuthKit must be
	// declared — behind an undeclared proxy every client shares the proxy's one
	// per-IP rate-limit bucket. One of the four is required.
	//
	// TrustedProxies are the CIDRs of reverse proxies / load balancers whose
	// X-Forwarded-For is honoured (walked right-to-left past our own hops).
	// CF-Connecting-IP is never trusted from these peers.
	TrustedProxies []string
	// CloudflareProxies are Cloudflare's published egress ranges: X-Forwarded-For
	// like a trusted proxy plus CF-Connecting-IP when that header is absent.
	// Set it ONLY where Cloudflare fronts the origin, and lock the origin down
	// to Cloudflare ingress.
	CloudflareProxies []string
	// DirectPeerIP asserts nothing sits in front: RemoteAddr IS the end client.
	DirectPeerIP bool
	// ClientIP is a bespoke extraction strategy. ADVANCED: it replaces the
	// proxy handling above entirely.
	ClientIP ClientIPFunc

	// Languages declares the supported UI languages; the zero value is
	// English-only.
	Languages LanguageConfig

	// Documents are the published-document providers (normally
	// *documents.Service values) served at the RouteDocuments mount and
	// stamped by the delegated-token mint route (#260/#261). Requires
	// embedded.Config.Documents.Readers.
	Documents []DocumentProvider
	// contains filtered or unexported fields
}

Config is the HTTP layer's configuration. Engine data lives in embedded.Config and engine dependencies in embedded.Deps; this is only what the transport itself decides: client-IP posture, rate limiting, languages, published documents.

func (Config) Validate added in v0.98.0

func (c Config) Validate() error

Validate checks the static configuration: parseable proxy CIDRs, one rate-limit choice, and a declared client-IP posture.

type DocumentProvider added in v0.91.0

type DocumentProvider interface {
	// Reference is the process snapshot reference stamped into minted tokens.
	Reference() documents.Reference
	// Lookup serves any persisted digest for the publication route.
	Lookup(ctx context.Context, digest string) (documents.SignedDocument, error)
	// CurrentDigest re-validates (and repairs) the persisted snapshot artifact.
	CurrentDigest(ctx context.Context) (string, error)
	// EnsureSigningKID re-signs the artifact when it is not signed by the key
	// that just minted a token referencing it.
	EnsureSigningKID(ctx context.Context, tokenKID string) error
}

DocumentProvider is the published-document seam shared by the publication route (#260) and the delegated-token mint's document stamping (#261). *documents.Service implements it.

type LanguageConfig

type LanguageConfig struct {
	Supported []string
	Default   string
}

LanguageConfig declares the supported UI languages and default. The query parameter name is NOT configurable — hardcoded to langSelector ("lang") (#143). No language config means English-only (Supported ["en"], default "en").

type MountOptions added in v0.84.0

type MountOptions struct {
	// Groups selects the mounted route groups. Nil mounts the default API
	// surface plus browser OIDC. Non-nil mounts exactly the named groups —
	// include RouteBrowserOIDC to keep the browser redirect flows.
	Groups []RouteGroup
	// APIPrefix anchors the JSON API routes. "" means DefaultAPIPrefix; "/"
	// mounts the API at root.
	APIPrefix string
	// ExcludeRoutes drops routes the host shadows with its own handlers.
	// Matched by method + prefix-neutral RouteSpec path. Exclusion does NOT
	// alter the verifier's MFA-enrollment exempt-path set — that is derived
	// from the full route registry at authhttp.New time, so a shadowed enroll
	// route stays reachable through the host's replacement.
	ExcludeRoutes []RouteRef
	// Wrap decorates every RouteSpec-backed handler (API + browser OIDC) at
	// mount time. JWKS is not wrapped (it carries no RouteSpec).
	Wrap func(RouteSpec, http.Handler) http.Handler
	// RefreshCookie (ak#271) delivers the rotating refresh token as an
	// HttpOnly+Secure+SameSite=Lax cookie (RefreshCookieName) instead of a JSON
	// body field, so an injected script cannot read the durable credential.
	// OFF by default: a host that leaves this false gets byte-identical
	// behaviour, refresh_token in every body as before.
	//
	// When on, every session-establishing response sets the cookie and omits
	// refresh_token from its body/fragment/postMessage payload; POST /token
	// accepts the cookie when the body carries no token (body still wins, so a
	// mid-migration client is never stranded); and DELETE /logout clears it.
	// The cookie is Path-scoped to this mount's POST /token — the only route
	// that reads a refresh token — so it never rides the SPA document or assets.
	//
	// Browser-facing by construction: the host must serve the SPA and this
	// mount on the SAME origin, or the cookie never reaches the refresh call.
	RefreshCookie bool
}

MountOptions configures the combined AuthKit surface (MountHandler).

type RateLimitResult

type RateLimitResult struct {
	Allowed      bool
	RetryAfter   time.Duration
	Availability *ActionAvailability
}

type RateLimiter

type RateLimiter interface {
	AllowNamed(bucket string, key string) (bool, error)
}

RateLimiter is a minimal interface used by adapters.

type RateLimiterWithResult

type RateLimiterWithResult interface {
	AllowNamedResult(bucket string, key string) (ratelimit.Result, error)
}

type RouteAuthTier added in v0.98.0

type RouteAuthTier string

RouteAuth is the authentication tier a route enforces before its handler runs (#328).

const (
	AuthPublic     RouteAuthTier = "public"     // no principal
	AuthOptional   RouteAuthTier = "optional"   // principal used when present
	AuthRequired   RouteAuthTier = "required"   // valid principal
	AuthPermission RouteAuthTier = "permission" // valid principal holding RouteSpec.Permission
	AuthSigned     RouteAuthTier = "signed"     // per-message proof (domain fetch / JWS)
)

type RouteGroup

type RouteGroup string

RouteGroup identifies a prefix-neutral AuthKit route capability. Host applications can mount all default groups or select only the capabilities they want to expose.

const (
	RouteAuth RouteGroup = "auth"
	// RouteDeviceKeys is the refreshless native-client login surface: email
	// enrollment plus Ed25519 challenge authentication.
	RouteDeviceKeys       RouteGroup = "device_keys"
	RouteRegistration     RouteGroup = "registration"
	RouteAccount          RouteGroup = "account"
	RouteAdmin            RouteGroup = "admin"
	RoutePermissionGroups RouteGroup = "permission_groups"
	RouteBrowserOIDC      RouteGroup = "browser_oidc"
	// RouteApplications is the #264 application self-registration surface
	// (register / rotate / repoint). Mounted only when the host enables
	// Config.Applications.SelfRegistration.
	RouteApplications RouteGroup = "applications"
	// RouteDelegated is the #261 delegated-token mint surface
	// (POST /delegated/token). Mounted only when Config.Delegated declares an
	// audience allowlist.
	RouteDelegated RouteGroup = "delegated"
	// RouteDocuments is the #260 published signed-document surface
	// (GET|HEAD /.well-known/authkit/documents/{digest} — root-anchored like
	// JWKS, not under the API prefix). Mounted only when document providers
	// are wired via WithDocuments.
	RouteDocuments RouteGroup = "documents"
)

type RouteRef added in v0.84.0

type RouteRef struct {
	Method string
	Path   string
}

RouteRef identifies a route by HTTP method and prefix-neutral RouteSpec path (e.g. "/admin/users", NOT "/api/v1/admin/users"). JWKS is addressable as GET JWKSPath.

type RouteSpec

type RouteSpec struct {
	Method  string
	Path    string
	Group   RouteGroup
	Handler http.Handler
	// Auth is the tier the handler wrapper enforces before the handler runs;
	// Permission names the root/group permission for AuthPermission (#328).
	Auth       RouteAuthTier
	Permission authkit.Perm
	// Bucket is the per-IP rate-limit bucket APIRoutes applies in front of the
	// handler ("" = none). Per-identifier and branch-specific buckets stay in
	// the handler.
	Bucket string
	// MFAEnrollmentExempt marks a route as part of the 2FA enroll/challenge/
	// verify surface a forced-enrollment-gated user (verify.WithRequireMFAEnrollment)
	// must still be able to reach. authhttp.New derives the verifier's exempt-path
	// allowlist from routes tagged here (#243) — the route table is the single
	// source of truth, so a rename/add stays consistent by construction.
	MFAEnrollmentExempt bool
}

RouteSpec is a concrete, prefix-neutral route with its AuthKit handler attached. Path parameters use net/http ServeMux syntax, e.g. "/namespaces/{slug}".

func (RouteSpec) Requires added in v0.98.0

func (r RouteSpec) Requires() string

Requires names the configuration that mounts the route ("" = always mounted). It mirrors the APIRoutes/OIDCBrowserRoutes filters, so the generated route documentation and the mount decision cannot drift.

type Service

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

Service wraps the internal AuthKit engine with net/http mounting helpers.

func New added in v0.80.0

func New(client *embedded.Client, hcfg Config) (*Service, error)

New constructs the HTTP adapter over a client the host already built — client-first construction (#142). The host wires the engine and its dependencies on embedded.New; New takes only the HTTP layer's Config. Postgres is REQUIRED: the durable user/role and permission-group store has no in-memory fallback (#106), so the client must be Postgres-backed; pure token verification with no storage uses verify.NewVerifier instead.

Construction fails (returns an error, never panics — #212) when the configuration cannot be served: Config.Validate refuses a missing client-IP posture or a bad CIDR, and the cross-layer checks refuse a "required" registration verification with no sender (Deps.Email / Deps.SMS), document providers without readers, and a delegated route without its authorizer.

Redis is taken ONCE (#210): the engine's Redis client (Deps.Redis) also backs the HTTP layer's OIDC/SIWS state caches and rate limiter; Config.Redis is an override, not a requirement.

client, err := embedded.New(cfg, embedded.Deps{Postgres: pg, Redis: rdb, Email: mailer})
srv, err := authhttp.New(client, authhttp.Config{TrustedProxies: []string{"10.0.0.0/8"}})

func (*Service) APIRoutes

func (s *Service) APIRoutes(groups ...RouteGroup) []RouteSpec

APIRoutes returns AuthKit's enabled JSON API routes. With no groups it returns the default API surface. With groups, it returns only matching routes.

func (*Service) CheckSMSHealth

func (s *Service) CheckSMSHealth(ctx context.Context) error

CheckSMSHealth probes (without sending an SMS) whether the configured sender can actually deliver, caching the result to gate phone-based flows. Returns the probe error (nil = healthy) so the host app can log it at startup.

func (*Service) Close added in v0.98.0

func (s *Service) Close()

Close stops the background work New started (the in-memory limiter's sweep). Idempotent; safe on a nil Service.

func (*Service) JWKSHandler

func (s *Service) JWKSHandler() http.Handler

JWKSHandler returns a handler for GET /.well-known/jwks.json.

func (*Service) OIDCBrowserRoutes

func (s *Service) OIDCBrowserRoutes(groups ...RouteGroup) []RouteSpec

OIDCBrowserRoutes returns browser redirect routes with no mount prefix.

func (*Service) PermissionGroupRoutes

func (s *Service) PermissionGroupRoutes() []RouteSpec

PermissionGroupRoutes returns the auto-generated management routes implied by this Service's declared permission-group schema, plus the cross-persona GET /me/groups discovery route. Mirrors APIRoutes: prefix-neutral RouteSpecs in the RoutePermissionGroups group, language-wrapped and auth-required. The set is fully config-derived from svc.PermissionGroupSchema().GeneratedRoutes(); a capability a profile disables is simply absent (=> 404).

func (*Service) SMSAvailable

func (s *Service) SMSAvailable() bool

SMSAvailable reports whether phone-based flows should be offered (a sender is configured and, if checked, found able to deliver).

func (*Service) SMSHealthy

func (s *Service) SMSHealthy() bool

SMSHealthy reports the last CheckSMSHealth result (true until a check runs).

func (*Service) Verifier

func (s *Service) Verifier() *verify.Verifier

Verifier returns the server's token verifier. The engine is the *embedded.Client the host built and passed to New; the transport does not vend it back (client-first, #142).

Jump to

Keyboard shortcuts

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