egauth

package module
v0.9.1-0...-ae81530 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 2 Imported by: 0

README

egauth

Go Reference CI

A complete, composable, non-opinionated authentication toolkit for Go.

About this project — please read. egauth was built mostly through "vibe coding" (AI-assisted development). egauth's security review to date is an AI-driven audit only; it has not had an independent third-party human security audit, and that risk is accepted for v1.0 — pin a reviewed commit, commission your own audit, or wait if that trade-off is unacceptable. I wrote it because I wanted a library like this for my own projects. The code is engineered carefully and secure-by-default — an adversarial pass found no high/critical issue — but "AI-audited" is not a synonym for "audited", so weigh the status accordingly before using it for anything sensitive. The full audit status, scope, and escape hatch live in AUDIT.md.

Comments, reviews, issues, and security audits are genuinely welcome. If you spot something — a bug, a design smell, a crypto/protocol concern — please open an issue or PR; for anything security-sensitive see SECURITY.md. Independent scrutiny is exactly what this project wants.

egauth is a set of independent modules in the style of the standard library's database/sql: you import the ones you need and wire them together with dependency injection. There is no framework to adopt — egauth never owns your HTTP router, your database access, or your conventions. Every module exposes a Service interface plus a Store contract, with in-memory and PostgreSQL (pgx) backends behind a shared cross-backend conformance suite.

Status: The API is still settling; see Stability.

Modules

Module What it does
identity Accounts & credentials: register, login, password reset, email verification, magic link, change-password/email, account deletion, OAuth identity linking; forced-password-change for temporary credentials (AdminCreateUser, SetTemporaryPassword)
tokens Stateless JWT access tokens (pluggable symmetric HS256 or asymmetric RS256/ES256/EdDSA signing, publishable JWKS) + single-use refresh tokens with rotation & theft detection; API keys (PAT & service tokens) with a full lifecycle — issue, list, revoke. Reference impl in tokens/jwt
sessions Server-side, revocable sessions with idle-timeout (Touch) and fixation defense (Rotate)
passwords Hashing/policy/breach seams + references: argon2, policy, breach/hibp, breach/offline
mfa TOTP (RFC 6238) with recovery codes
otp One-time codes (email/SMS), enumeration-safe HTTP handlers
passkey WebAuthn / passkeys, including discoverable (usernameless) login
oauth OAuth2 / OIDC, PKCE-S256, id_token/nonce/JWKS; 12 built-in providers (Apple, Auth0, Cognito, Discord, Facebook, GitHub, GitLab, Google, Keycloak, LinkedIn, Microsoft, Okta) in oauth/providers
identity.Mailer / identity.SMSSender Bring-your-own-delivery seams: wire your own SMTP, SendGrid, Twilio, etc. — egauth never sends mail or SMS itself
ratelimit, event, health Pluggable rate-limiting, audit-event, and readiness seams

Install

go get github.com/JLugagne/egauth

The PostgreSQL storage backend lives in a separate module (adapters/pgx) so core consumers never pull the pgx driver or the testcontainers/Docker chain into their dependency graph. Install it only if you use the Postgres stores:

go get github.com/JLugagne/egauth/adapters/pgx

Requires Go 1.26+.

Quickstart: login + refresh

The recommended stateless stack is identity (verify credentials) + tokens/jwt (issue and rotate tokens). This wires it with the in-memory backends; swap in the pgx stores for production.

Most applications carry no custom data in their tokens. For that common case use the tokens/basic convenience layer, which specializes the token API to "no custom claims" so you never spell the [struct{}] type argument. (Need custom claims? Use the generic API directly with your own type — see below.) This is the runnable package example (go test ./tokens/basic -run Example).

ctx := context.Background()
const tenant = "" // empty string is the single-tenant default partition

// identity: credential verification + account lifecycle
idStore := identitymem.NewStore() // identity/memory; or adapters/pgx/identity.NewStore(pool)
svc := identity.NewService(idStore, argon2.NewHasher(), policy.NewDefaultPolicy())

// tokens: stateless access tokens + refresh rotation (no custom claims).
// claimsProvider re-derives a user's claims on every refresh, so a disabled or
// role-changed user is re-evaluated rather than frozen at login.
claimsProvider := basic.ClaimsProviderFunc(
    func(_ context.Context, userID uuid.UUID, tenantID string) (basic.Claims, error) {
        return basic.Claims{Subject: userID, TenantID: tenantID}, nil
    },
)
tokenStore := basic.NewMemoryStore() // tokens/memory; or adapters/pgx/tokens.NewStore(pool)
issuer := basic.NewIssuer(basic.Config{
    Store:          tokenStore,
    Issuer:         "example-app",
    SecretKey:      hs256SecretFromYourSecretStore, // >= 32 bytes
    AccessTTL:      15 * time.Minute,
    RefreshTTL:     720 * time.Hour,
    ClaimsProvider: claimsProvider, // required for Rotate (refresh)
})

// register, authenticate, issue a token pair
user, err := svc.Register(ctx, tenant, "alice@example.com", password)
// ... handle err
pair, err := issuer.IssueTokenPair(ctx, basic.Claims{Subject: user.ID, TenantID: tenant})

// later: refresh — rotation single-use-consumes the old refresh token
// (replaying it trips theft detection and revokes the family)
next, err := issuer.Rotate(ctx, tenant, pair.RefreshToken)
Over HTTP

The handlers are à-la-carte http.HandlerFunc factories you mount on your own mux — egauth imposes no router:

claimsOf := func(u *identity.User) basic.Claims {
    return basic.Claims{Subject: u.ID, TenantID: u.TenantID}
}
mux := http.NewServeMux()
mux.Handle("POST /login",   identity.LoginHandler(svc, issuer, claimsOf))
mux.Handle("POST /refresh", basic.RefreshHandler(issuer))    // issuer is the Rotator
mux.Handle("POST /logout",  basic.LogoutHandler(tokenStore)) // revokes the refresh family

// protect a route with the access-token middleware:
mux.Handle("GET /me", basic.RequireAuth(issuer,
    func(w http.ResponseWriter, r *http.Request, actor egauth.Actor, _ struct{}) {
        // actor.UserID / actor.TenantID are authenticated
    }))
With custom claims

To carry application data in your tokens, use the generic tokens / tokens/jwt API directly with your own claims type C. The shape is identical — basic is only a thin facade over it — but every token type takes the type argument: tokens.Claims[C], jwt.New[C], tokens.RequireAuth[C], and so on. Use tokens.Claims[struct{}] if you ever need the generic form with no custom claims. See go doc github.com/JLugagne/egauth/tokens.

For asymmetric signing (so verifiers hold only a public key), set jwt.Config.Signers with jwt.NewRSASigner / jwt.NewECDSASigner / jwt.NewEdDSASigner instead of SecretKey, and serve the public keys from Service.PublicJWKS(). The default tokens/basic facade stays HS256-only; asymmetric signing uses the generic tokens/jwt API.

API keys: PAT and service tokens

egauth issues two kinds of long-lived key through IssueAPIKey.

Kind Actor.Kind IsHuman() Subject Typical use
PAT (Personal Access Token) egauth.PAT true The owning user's ID CI tokens, script automation acting on behalf of a specific user
Service token egauth.Service false (IsMachine() true) The key's own ID Background jobs, inter-service calls with no human owner

Set the type at issuance:

// PAT — acts on behalf of user alice
pat, err := issuer.IssueAPIKey(ctx, "sk_", tokens.KeyTypePAT, alice.ID, tokens.Claims[C]{
    Subject:  alice.ID,
    TenantID: tenant,
    Scopes:   []string{"repo:read", "issues:write"},
})

// Service token — machine identity; Subject becomes the key's own ID after issuance
svcToken, err := issuer.IssueAPIKey(ctx, "svc_", tokens.KeyTypeService, operator.ID, tokens.Claims[C]{
    TenantID: tenant,
    Scopes:   []string{"metrics:ingest"},
})

Authority model — IssueAPIKey does not copy the issuing user's roles. The key's authority is only the scopes you pass at issuance. egauth never silently inherits the creating user's live roles: a PAT with Scopes: ["repo:read"] can only read repositories even if the user can do far more. This is the safe default (a leaked PAT is bounded); if you want a richer scope set, pass it explicitly. See SECURITY.md for the full security model.

Listing and revoking keys

Keys have a full server-side lifecycle, and the clear-text key value is never retrievable after issuance — egauth stores only a SHA-256 hash, so a leaked database never yields a usable key.

// List every key a user created — active and revoked — for a management UI.
// Listed keys carry a blank Token (the secret only ever exists at creation) and a
// populated RevokedAt on revoked keys.
keys, err := issuer.ListAPIKeysByCreator(ctx, tenant, alice.ID)

// Revoke a key by its ID — you never need to hold the secret to revoke it.
err = issuer.RevokeAPIKey(ctx, tenant, keys[0].ID)

Revocation is a soft-revoke: the row is kept (so the key stays visible to audit/management with a RevokedAt timestamp) and VerifyAPIKey returns tokens.ErrAPIKeyRevoked for it from then on — a distinct outcome from expiry (ErrTokenExpired) and from a missing key (ErrAPIKeyNotFound). The in-memory and PostgreSQL backends enforce identical behaviour through one shared contract suite, so neither can silently diverge.

Opt-in route gates

RequireAuth accepts additional AuthOptions that gate routes on the principal's kind or scopes. All gates are opt-in — the library imposes no default authority policy.

// Restrict a route to machine (Service) callers only:
mux.Handle("/internal/ingest", basic.RequireAuth(issuer, ingestHandler,
    tokens.WithRequiredKind[struct{}](egauth.Service),
))

// Restrict a route to callers carrying a specific scope:
mux.Handle("/api/admin", basic.RequireAuth(issuer, adminHandler,
    tokens.WithRequiredScopes[struct{}]("admin:write"),
))

// Convenience wrappers for kind gating:
tokens.RequireMachine[C]() // equivalent to WithRequiredKind(egauth.Service)
tokens.RequireHuman[C]()   // equivalent to WithRequiredKind(egauth.User, egauth.PAT)

On a kind or scope mismatch the middleware returns 403 wrong_principal_kind or 403 insufficient_scope respectively — the caller is authenticated but not allowed on this route. The egauth.Actor injected into the handler carries Kind, KeyID, and Scopes, plus HasScope / HasAllScopes / HasAnyScope helpers, so the application can also branch or enforce policy directly without middleware gates.

For authorization the fixed gates don't cover, WithGate runs an application-supplied predicate over the verified egauth.Actor and your custom claims C — one flexible evaluator instead of a fixed policy vocabulary. It is opt-in like every other gate, and egauth still assigns no meaning to your scopes or claims; your function decides.

mux.Handle("/api/reports", basic.RequireAuth(issuer, reportsHandler,
    tokens.WithGate[struct{}](func(actor egauth.Actor, _ struct{}) error {
        if !actor.HasAllScopes("reports:read") {
            return errors.New("missing reports:read")
        }
        return nil // nil = allowed; any error → 403
    }),
))
Audit events for key lifecycle

Wire event.Sink to observe the full key lifecycle:

Event When Key Attrs
api_key.created Key issued "key_type" (pat/service), "created_by" (user UUID)
api_key.revoked Key revoked via RevokeAPIKey "key_id" (the revoked key's UUID)
api_key.auth.succeeded Key verified successfully "key_type", "ip", "user_agent" (if RequestContext supplied)
api_key.auth.failed Verification failed Event.Reason = not_found / expired / revoked / tenant_mismatch / wrong_type
api_key.purged Expired keys swept by DeleteExpired "count" (number of rows deleted)

login.succeeded and login.failed carry "ip" / "user_agent" when the handler receives a event.RequestContext. Audit events never carry secrets, tokens, hashes or raw input — only short machine Reason codes and safe metadata. See SECURITY.md.

Multi-tenancy

Every tenant-scoped operation takes an explicit tenantID string argument. An empty string ("") is the valid single-tenant default partition — passing it explicitly keeps the tenant boundary visible at every call site (the defense against cross-tenant access / IDOR).

For a genuinely single-tenant application, wrap a Service once and drop the argument:

app := identity.NewSingleTenant(svc) // every call uses the empty tenant ("")
user, err := app.Register(ctx, "bob@example.com", password)

SingleTenant facades exist on identity, sessions, mfa, otp, passkey, and tokens/jwt.

Storage backends

Each module ships two interchangeable Store implementations behind one contract:

  • <module>/memory — zero-dependency, for tests and single-process apps; lives in the core module.
  • adapters/pgx/<module> — PostgreSQL via jackc/pgx, in the separate adapters/pgx module (go get github.com/JLugagne/egauth/adapters/pgx). Call Migrate(ctx, pool) once at startup (forward-only, versioned via a schema_migrations table; re-running is a no-op).
import identitypgx "github.com/JLugagne/egauth/adapters/pgx/identity"

pool, _ := pgxpool.New(ctx, dsn)
_ = identitypgx.Migrate(ctx, pool)
store := identitypgx.NewStore(pool)

Security

egauth is enumeration-safe by default (uniform responses + decoy hashing), enforces brute-force lockout, pins each JWT to its key's algorithm (rejecting none/alg-confusion) for symmetric (HS256) or asymmetric (RS256/ES256/EdDSA) signing, rotates refresh tokens with family-based theft detection, stores only SHA-256 hashes of refresh/API/session/OTP secrets, and caps pre-auth body size against hashing-DoS. Credential-bearing types redact their secrets on fmt/slog. Read SECURITY.md for the full model — including the explicit trade-offs (e.g. TOTP secrets stored recoverably, accepted account-existence disclosures) and the boundaries egauth leaves to the application (CSRF tokens, rate-limit policy, mail/SMS transport, observability, idempotency).

Forced-password-change for temporary credentials. Provision a one-time credential via identity.AdminCreateUser (admin-created account) or identity.SetTemporaryPassword (admin-issued temporary password); both flag the credential so the user must choose a new password at next login. A flagged login issues a full, renewable pair carrying tokens.Claims.MustChangePassword=true; the flag is recorded on the refresh-token family and carried onto every silent refresh, so mounting tokens.WithPasswordChangeGate on your protected routes keeps soft-redirecting to the reset page until the password is changed — a user cannot escape by waiting for the access token to expire. The credential stays valid throughout — never a lockout. egauth does NOT do periodic, age-based rotation (NIST SP 800-63B discourages fixed-interval expiry). See SECURITY.md for the full semantics.

Observability — wire your metrics/audit pipeline to event.Sink. Use event.NewSlogSink for the common structured-logging case, or github.com/JLugagne/egauth/adapters/otel for OpenTelemetry spans (NewSpanSink creates one child span per security event with egauth.* attributes). Combine them with event.MultiSink. Request-level idempotency is the application layer's responsibility. See SECURITY.md § Observability and idempotency.

Reference application

examples/fullstack is a self-contained runnable application that wires the full egauth stack — identity + tokens with custom claims + MFA (TOTP) + passkey + admin operations + audit events — over HTTP using only in-memory backends and the standard library mux. It builds from the module proxy with no local go.work workspace:

go run github.com/JLugagne/egauth/examples/fullstack@latest

A smoke test (go test ./examples/fullstack) exercises all six concerns end-to-end.

Documentation

Full API reference: pkg.go.dev/github.com/JLugagne/egauth

Each module has a package overview (go doc github.com/JLugagne/egauth/identity) and the login-critical packages carry runnable examples.

Production: evict in-memory stores

The sessions/memory, otp/memory, and ratelimit.TokenBucket backends grow without bound unless their eviction methods (DeleteExpired / Cleanup) are called periodically. In any non-trivial production deployment you must schedule this — a flood of unique keys, sessions, or OTP codes otherwise exhausts available memory. Use the optional janitor helper:

import "github.com/JLugagne/egauth/janitor"

j := janitor.Start(ctx, 5*time.Minute, func() {
    sessStore.DeleteExpired(context.Background(), tenantID)
})
defer j.Stop()

janitor.Start accepts any func(), so the same pattern covers otpStore.DeleteExpired and tokenBucket.Cleanup. For production deployments beyond a single binary, swap the in-memory stores for their pgx counterparts (which rely on the database for eviction instead).

Stability

Pre-1.0: the API may change between minor versions until it settles, at which point releases will follow SemVer with a CHANGELOG. Pin a commit or tag in go.mod for reproducible builds.

Go version support policy.

  • For v1.x and later: The go.mod go directive is pinned for the life of the major version. v1.0 through v1.x will all require go 1.26 (the minimum toolchain at v1.0 release). Bumping to a newer major Go release is deferred to v2. This provides maximum build stability within a major version.

  • Pre-v1 releases: egauth targets the newest major Go release as its minimum toolchain. The go.mod directive is bumped deliberately — each time a new major Go version ships, the floor moves up to it. This is an intentional choice (not an accident): the library is expected to be adopted in greenfield projects that run the current toolchain. If you need support for an older Go version, pin an earlier egauth release.

Documentation

Overview

Package egauth is a composable authentication toolkit for Go: a set of independent modules you import à la carte and wire together yourself, in the style of the standard library's database/sql — rather than a framework that owns your HTTP router, your database access, or your conventions. The root package itself is deliberately tiny: it exports only Actor, the explicit authenticated-principal value passed to handlers (never smuggled through context.Context). All behavior lives in the sub-packages below.

Modules

identity   Accounts & credentials: register, login (Authenticate), password reset, email
           verification, magic-link login, change-password / change-email, phone verification,
           an independent recovery channel, account deletion, and OAuth identity linking.
tokens     Stateless JWT access tokens + single-use refresh tokens with rotation and theft
           detection, plus API keys. Reference implementation in tokens/jwt.
sessions   Server-side, revocable sessions with idle-timeout (Touch) and fixation defense
           (Rotate).
passwords  Hashing / policy / breach-check seams plus references: passwords/argon2,
           passwords/policy, passwords/breach/hibp, passwords/breach/offline.
mfa        TOTP (RFC 6238) with recovery codes. (SMS is intentionally excluded as a factor.)
otp        One-time codes (email/SMS), with enumeration-safe HTTP handlers.
passkey    WebAuthn / passkeys, including discoverable (usernameless) login.
oauth      OAuth2 / OIDC with PKCE-S256 and id_token/nonce/JWKS; ready-made providers
           (Google, GitHub, Microsoft, Apple, Okta, Auth0, ...) live in oauth/providers.
ratelimit  Pluggable rate-limiting Limiter + token-bucket reference + middleware.
event      Dependency-free security-event Sink seam (audit logging, slog adapter).
health     Optional Store Ping/readiness seam.

Composable by design

There is no top-level constructor that bundles everything, and that is intentional: each module has its own Service interface, its own Store contract (with in-memory and pgx backends behind a shared cross-backend conformance suite), and functional-option dependency injection. You compose exactly the stack you need. A typical password-login deployment wires identity (verifies credentials and manages the account lifecycle) with tokens (issues the access/refresh pair) — identity never issues tokens or sessions itself, so you pick the token backend that fits.

idStore := identitymem.NewStore()                          // or adapters/pgx/identity.NewStore(pool)
idSvc := identity.NewService(idStore, argon2.NewHasher(), policy.NewDefaultPolicy())

tkStore := basic.NewMemoryStore()                          // tokens/basic: the no-custom-claims path
issuer := basic.NewIssuer(basic.Config{                    // thin tokens/jwt facade, zero [struct{}]
	Store: tkStore, Issuer: "example-app", SecretKey: secret,
	AccessTTL: 15 * time.Minute, RefreshTTL: 720 * time.Hour,
})

user, _ := idSvc.Register(ctx, tenantID, email, password)
pair, _ := issuer.IssueTokenPair(ctx, basic.Claims{Subject: user.ID, TenantID: tenantID})

The complete, runnable login + refresh wiring (including the HTTP handlers) lives in the identity package's example tests — see Example, ExampleNewSingleTenant and ExampleLoginHandler.

Multi-tenancy

Multi-tenancy is explicit and pervasive: every Store and Service operation takes a tenantID string. The empty string is the valid single-tenant default partition, so a single-tenant application simply passes "" — or wraps a Service in that module's SingleTenant facade (e.g. identity.NewSingleTenant) to drop the argument from every call.

Security and stability

egauth is security-literate by default (Argon2id, enumeration-resistant auth paths, refresh rotation with theft detection, alg-pinned JWTs, secure-by-default cookies, pre-auth body caps). The full threat model is in SECURITY.md; the module overview and a copy-pasteable quickstart are in README.md. The API is pre-1.0 and still settling; pin a commit or tag in go.mod for reproducible builds (see the Stability section of README.md).

Audit status: egauth's security review to date is an AI-driven audit only; it has not had an independent third-party human security audit, and that risk is accepted for v1.0 — pin a reviewed commit, commission your own audit, or wait if that trade-off is unacceptable. "AI-audited" is not a synonym for "audited". See AUDIT.md for the full review scope and the cautious-user escape hatch.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContextWithActor added in v0.9.0

func ContextWithActor(ctx context.Context, actor Actor) context.Context

ContextWithActor returns a copy of parent context with actor attached.

Types

type Actor

type Actor struct {
	// UserID is the user's UUID. Set for User and PAT actors; for Service actors the subject
	// is the key's own ID, which is stored in KeyID rather than here.
	UserID uuid.UUID
	// TenantID is the tenant scope in which this actor operates.
	TenantID string
	// Kind classifies the actor as User, PAT, or Service. The zero value behaves as User when UserID is set.
	Kind PrincipalKind
	// KeyID is the API key UUID. Non-zero for PAT and Service actors; empty for User actors.
	KeyID uuid.UUID
	// Scopes holds the set of permission scopes carried by this actor's token. egauth does not
	// interpret or enforce scopes — they are provided verbatim for the application's middleware
	// to act on (e.g. via WithRequiredScopes).
	Scopes []string
	// Roles holds the role identifiers assigned to this actor.
	Roles []string
	// Groups holds the group memberships of this actor.
	Groups []string
}

Actor represents the authenticated entity making a request. It is explicitly passed as an argument to handlers, never transported via context.Context.

Kind classifies the principal as a human (User or PAT) or a machine (Service). When UserID is set, an Actor with Kind == "" is treated as User by IsHuman/IsMachine.

func ActorFromContext added in v0.9.0

func ActorFromContext(ctx context.Context) (Actor, bool)

ActorFromContext returns the Actor attached to ctx, if any.

func (Actor) HasAllGroups added in v0.9.0

func (a Actor) HasAllGroups(groups ...string) bool

HasAllGroups reports whether every requested group is present in the actor's Groups list. Vacuous truth: calling with no arguments always returns true. It returns false for a nil or empty Groups slice when at least one group is requested.

func (Actor) HasAllRoles added in v0.9.0

func (a Actor) HasAllRoles(roles ...string) bool

HasAllRoles reports whether every requested role is present in the actor's Roles list. Vacuous truth: calling with no arguments always returns true. It returns false for a nil or empty Roles slice when at least one role is requested.

func (Actor) HasAllScopes added in v0.6.0

func (a Actor) HasAllScopes(scopes ...string) bool

HasAllScopes reports whether every requested scope is present in the actor's Scopes list. Vacuous truth: calling with no arguments always returns true. It returns false for a nil or empty Scopes slice when at least one scope is requested.

func (Actor) HasAnyGroup added in v0.9.0

func (a Actor) HasAnyGroup(groups ...string) bool

HasAnyGroup reports whether at least one of the requested groups is present in the actor's Groups list. Calling with no arguments always returns false (no group can satisfy an empty requirement set). It returns false for a nil or empty Groups slice.

func (Actor) HasAnyRole added in v0.9.0

func (a Actor) HasAnyRole(roles ...string) bool

HasAnyRole reports whether at least one of the requested roles is present in the actor's Roles list. Calling with no arguments always returns false (no role can satisfy an empty requirement set). It returns false for a nil or empty Roles slice.

func (Actor) HasAnyScope added in v0.6.0

func (a Actor) HasAnyScope(scopes ...string) bool

HasAnyScope reports whether at least one of the requested scopes is present in the actor's Scopes list. Calling with no arguments always returns false (no scope can satisfy an empty requirement set). It returns false for a nil or empty Scopes slice.

func (Actor) HasGroup added in v0.9.0

func (a Actor) HasGroup(group string) bool

HasGroup reports whether group g is present in the actor's Groups list. It returns false for a nil or empty Groups slice.

func (Actor) HasRole added in v0.9.0

func (a Actor) HasRole(role string) bool

HasRole reports whether role r is present in the actor's Roles list. It returns false for a nil or empty Roles slice.

func (Actor) HasScope added in v0.6.0

func (a Actor) HasScope(s string) bool

HasScope reports whether scope s is present in the actor's Scopes list. It returns false for a nil or empty Scopes slice.

func (Actor) IsAnonymous added in v0.9.0

func (a Actor) IsAnonymous() bool

IsAnonymous reports whether the actor represents an unauthenticated/anonymous entity. It returns true when both UserID and KeyID are nil UUIDs.

func (Actor) IsHuman added in v0.6.0

func (a Actor) IsHuman() bool

IsHuman reports whether the actor represents a human-initiated request. It returns true for User (interactive login) and PAT (personal access token acting on behalf of a user), and for the zero value of Kind, provided UserID is non-nil. An anonymous entity (UserID == uuid.Nil) always returns false.

func (Actor) IsMachine added in v0.6.0

func (a Actor) IsMachine() bool

IsMachine reports whether the actor represents a machine/service identity. It returns true only when Kind is Service.

type PrincipalKind added in v0.6.0

type PrincipalKind string

PrincipalKind classifies the authenticated entity making a request. It lets egauth tell the application whether a request is a user action or a machine action without requiring the application to inspect token internals.

The zero value is the empty string, which Actor.IsHuman treats as User when Actor.UserID is non-nil.

const (
	// User indicates an interactively authenticated human (session or short-lived JWT).
	// IsHuman returns true; IsMachine returns false.
	User PrincipalKind = "user"

	// PAT indicates a Personal Access Token that acts on behalf of a human.
	// The token carries explicit Scopes; the underlying subject is still the owning user.
	// IsHuman returns true; IsMachine returns false.
	PAT PrincipalKind = "pat"

	// Service indicates a machine/service identity decoupled from any human.
	// The token's subject is the key's own ID (not a user). IsMachine returns true.
	Service PrincipalKind = "service"
)

Directories

Path Synopsis
adapters
otel module
pgx module
Package event defines egauth's optional security-event seam.
Package event defines egauth's optional security-event seam.
examples
fullstack command
Package main is a runnable reference application that wires the full egauth stack: identity + tokens with custom claims + MFA (TOTP) + passkey + admin operations + audit events — all over HTTP using only in-memory backends and the standard library mux.
Package main is a runnable reference application that wires the full egauth stack: identity + tokens with custom claims + MFA (TOTP) + passkey + admin operations + audit events — all over HTTP using only in-memory backends and the standard library mux.
Package health defines the optional health-check seam implemented by egauth's pgx-backed stores, so readiness/liveness probes can be written against any store without depending on a specific backend or holding a separate handle to the underlying connection pool.
Package health defines the optional health-check seam implemented by egauth's pgx-backed stores, so readiness/liveness probes can be written against any store without depending on a specific backend or holding a separate handle to the underlying connection pool.
Package identity is egauth's account and credential-verification module: registration, password login (Authenticate), password reset, email verification, magic-link login, authenticated change-password / change-email, account deletion, and just-in-time provisioning of external (OAuth) identities.
Package identity is egauth's account and credential-verification module: registration, password login (Authenticate), password reset, email verification, magic-link login, authenticated change-password / change-email, account deletion, and just-in-time provisioning of external (OAuth) identities.
internal
doctest command
Command doctest guards the project's prose against API drift.
Command doctest guards the project's prose against API drift.
httputil
Package httputil provides shared HTTP helpers used across egauth handler packages.
Package httputil provides shared HTTP helpers used across egauth handler packages.
Package janitor provides a lightweight, optional ticker-based eviction helper for egauth's in-memory stores and rate-limit buckets.
Package janitor provides a lightweight, optional ticker-based eviction helper for egauth's in-memory stores and rate-limit buckets.
Package keystore provides per-tenant cryptographic isolation for egauth.
Package keystore provides per-tenant cryptographic isolation for egauth.
keystoretest
Package keystoretest is the conformance suite every keystore.Store backend must pass.
Package keystoretest is the conformance suite every keystore.Store backend must pass.
memory
Package memory is the zero-dependency, in-process keystore.Store backend.
Package memory is the zero-dependency, in-process keystore.Store backend.
mfa
Package mfa implements multi-factor authentication: time-based one-time passwords (TOTP, RFC 6238 / RFC 4226) for authenticator apps, and single-use recovery codes.
Package mfa implements multi-factor authentication: time-based one-time passwords (TOTP, RFC 6238 / RFC 4226) for authenticator apps, and single-use recovery codes.
memory
Package memory provides an in-memory mfa.Store, primarily for tests and single-process use.
Package memory provides an in-memory mfa.Store, primarily for tests and single-process use.
storetest
Package storetest provides a shared contract test suite for mfa.Store implementations.
Package storetest provides a shared contract test suite for mfa.Store implementations.
Package oauth implements the OAuth2 authorization-code flow (with PKCE) as stateless, composable HTTP handlers.
Package oauth implements the OAuth2 authorization-code flow (with PKCE) as stateless, composable HTTP handlers.
providers
Package providers ships ready-made oauth.Provider constructors for well-known identity providers (Discord, GitHub, Google).
Package providers ships ready-made oauth.Provider constructors for well-known identity providers (Discord, GitHub, Google).
otp
Package otp implements short numeric one-time passcodes (e.g.
Package otp implements short numeric one-time passcodes (e.g.
memory
Package memory provides an in-memory otp.Store, primarily for tests and single-process use.
Package memory provides an in-memory otp.Store, primarily for tests and single-process use.
storetest
Package storetest provides a shared conformance suite for otp.Store implementations.
Package storetest provides a shared conformance suite for otp.Store implementations.
Package passkey implements WebAuthn / FIDO2 passkeys (registration and login ceremonies) on top of the go-webauthn library, following egauth's conventions: a credential Store (memory + pgx implementations with a shared contract), a Service that runs the ceremonies, and à-la-carte HTTP handlers.
Package passkey implements WebAuthn / FIDO2 passkeys (registration and login ceremonies) on top of the go-webauthn library, following egauth's conventions: a credential Store (memory + pgx implementations with a shared contract), a Service that runs the ceremonies, and à-la-carte HTTP handlers.
memory
Package memory provides an in-memory passkey.Store, primarily for tests and single-process deployments.
Package memory provides an in-memory passkey.Store, primarily for tests and single-process deployments.
passkeytest
Package passkeytest provides a software WebAuthn authenticator for integration testing passkey flows without network calls or a real hardware authenticator.
Package passkeytest provides a software WebAuthn authenticator for integration testing passkey flows without network calls or a real hardware authenticator.
storetest
Package storetest provides a shared conformance suite for passkey.Store implementations.
Package storetest provides a shared conformance suite for passkey.Store implementations.
Package passwords defines egauth's password seams — Hasher (hash and constant-time compare), Policy (validate a candidate password), and BreachChecker (k-anonymity breach lookup) — plus the shared error sentinels and the MaxPasswordLength pre-hash DoS cap.
Package passwords defines egauth's password seams — Hasher (hash and constant-time compare), Policy (validate a candidate password), and BreachChecker (k-anonymity breach lookup) — plus the shared error sentinels and the MaxPasswordLength pre-hash DoS cap.
breach/hibp
Package hibp implements passwords.BreachChecker against the Have I Been Pwned "Pwned Passwords" range API using k-anonymity: the password is hashed with SHA-1, and only the first five hex characters of that digest are ever sent to the service.
Package hibp implements passwords.BreachChecker against the Have I Been Pwned "Pwned Passwords" range API using k-anonymity: the password is hashed with SHA-1, and only the first five hex characters of that digest are ever sent to the service.
breach/offline
Package offline implements passwords.BreachChecker against an in-memory set of known- compromised password SHA-1 hashes loaded once at startup (for example from the downloadable HIBP "Pwned Passwords" offline corpus, or a custom blocklist).
Package offline implements passwords.BreachChecker against an in-memory set of known- compromised password SHA-1 hashes loaded once at startup (for example from the downloadable HIBP "Pwned Passwords" offline corpus, or a custom blocklist).
Package ratelimit provides a small, pluggable request-throttling seam for the egauth HTTP handlers and a dependency-free in-memory token-bucket reference implementation.
Package ratelimit provides a small, pluggable request-throttling seam for the egauth HTTP handlers and a dependency-free in-memory token-bucket reference implementation.
Package sessions is egauth's server-side session module: opaque session tokens backed by a store, with sliding idle-timeout (Touch), rotation against session fixation (Rotate), and revocation.
Package sessions is egauth's server-side session module: opaque session tokens backed by a store, with sliding idle-timeout (Touch), rotation against session fixation (Rotate), and revocation.
Package tokens is egauth's stateless-token module: JWT access tokens plus single-use refresh tokens with rotation, family-based reuse/theft detection, and long-lived API keys.
Package tokens is egauth's stateless-token module: JWT access tokens plus single-use refresh tokens with rotation, family-based reuse/theft detection, and long-lived API keys.
basic
Package basic is a non-generic convenience layer over the generic tokens API for the common case of an application that needs NO custom JWT claims.
Package basic is a non-generic convenience layer over the generic tokens API for the common case of an application that needs NO custom JWT claims.
jwt
CachingKeyStore wraps a KeyStore with a bounded-TTL, per-tenant in-memory cache so a single request (and the many that follow it within the TTL) does not re-hit the backing key store — typically a database — for every sign and verify.
CachingKeyStore wraps a KeyStore with a bounded-TTL, per-tenant in-memory cache so a single request (and the many that follow it within the TTL) does not re-hit the backing key store — typically a database — for every sign and verify.
Package webapp provides NewWebApp, a batteries-included preset that wires the identity and tokens packages into a single mounted http.Handler for the common password web-app case (no custom token claims), with secure-by-default cookies, CSRF and a non-nil event sink.
Package webapp provides NewWebApp, a batteries-included preset that wires the identity and tokens packages into a single mounted http.Handler for the common password web-app case (no custom token claims), with secure-by-default cookies, CSRF and a non-nil event sink.

Jump to

Keyboard shortcuts

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