Core overview
WIPAuthEngine is the single faceted root. Sessions, identities, passwords, providers, MFA, flows, API keys, M2M, orgs, idempotency, hijack policy, anomaly - all on one instance.
What is AuthEngine?
AuthEngine is the runtime entry point for duck-auth. You instantiate it once
at boot, wire it with the transport, stores, and providers you want, and
hand the instance to your framework adapter.
import { AuthEngine } from '@gentleduck/auth/core'
export const auth = new AuthEngine({ /* config */ })
Every concern is exposed as a typed facet on the instance. There is no hidden global, no module-level state, no implicit singleton.
The 14 facets
| Facet | What it owns |
|---|---|
sessions | Create / rotate / revoke / resolve sessions. The fixation-defence rotation matrix (DESIGN section 37) routes every privilege change through rotateOrCreate(). |
identities | CRUD + linking + merge + GDPR (soft-delete, restore, erase, exportAll, bulkCreate). Typed Profile generic. |
passwords | Hash / verify / needsRehash against the configured AuthHasher.IHasher. Auto-rehash on successful sign-in if hasher params drift. |
providers | Registry and dispatcher for every sign-in method. auth.providers.use(...) registers; the flows facet dispatches. |
mfa | TOTP (RFC 6238), backup codes (constant-time compare), WebAuthn-MFA factor enrolment. |
flows | High-level flows: signIn, signOut, signUp state machine, password reset, email change, impersonate. |
apiKeys | Create (plaintext shown once), list, revoke, rotate, verify, requireScopes. |
m2m | OAuth2 client-credentials on top of apiKeys. Adds a sign-in path when registered. |
orgs | Multi-org membership with typed OrgMeta. null if stores.orgs is omitted. |
plugins | Plugin registry - use(plugin), installed, facets, dispose. |
operations | Maintenance mode + read-only mode toggles. Causes AUTH/MAINTENANCE / AUTH/READONLY_MODE. |
idempotency | Request-dedupe cache (24h TTL default). Keyed by header idempotency-key. |
hijack | IP/UA drift policy - ignore / rotate / mfa / revoke. Triggers lockout event. |
anomaly | Detector registry. Aggregates detector signals into allow / deny / step-up. |
Constructor at a glance
import { AuthEngine, AuthInMemoryEvents, AuthScryptHasher } from '@gentleduck/auth/core'
import { CookieTransport } from '@gentleduck/auth/core/transport'
import { AuthMemoryAdapter } from '@gentleduck/auth/adapters/memory'
import { AuthMemoryLimiter } from '@gentleduck/auth/limiters/memory'
const adapter = new AuthMemoryAdapter()
export const auth = new AuthEngine({
baseUrl: 'http://localhost:3000',
transport: new CookieTransport({ secure: false }),
stores: {
identities: adapter.identities,
sessions: adapter.sessions,
credentials: adapter.credentials,
// orgs is optional - pass adapter.orgs to enable the orgs facet
},
events: new AuthInMemoryEvents(),
limiter: new AuthMemoryLimiter({ max: 5, windowMs: 60_000 }),
passwords: { hasher: new AuthScryptHasher() },
})
| Config key | Purpose |
|---|---|
baseUrl | Issuer for JWTs, OIDC discovery, signed redirect callbacks. |
transport | The session carrier (CookieTransport / BearerTransport / JwtTransport / CompositeTransport). |
stores | identities, sessions, credentials, optionally orgs. Implements the per-table store interfaces. |
limiter | Rate-limiter. Use AuthMemoryLimiter in dev; Redis-backed in production. strict() rejects AuthNoopLimiter in production. |
session | Per-session TTLs: ttlMs (default 7d), absoluteTtlMs (default 30d), freshnessMs (default 5m). |
identities | softDeleteGracePeriodMs (default 7d), profileMaxBytes (default 16 KiB). |
passwords | hasher plus optional minLength / maxLength / rejectCommon. |
providers | Array of AuthProvider.IProvider instances to register at boot. |
events | AuthEvents.IBus. Default is AuthInMemoryEvents - swap to AuthRedisEvents or a custom bus for fan-out. |
mfa | issuer, backupCodeCount, backupCodeLen. |
apiKeys | prefix (e.g. ak_live_), randomBytes. |
hijack | Detection sensitivity + reaction (ignore/rotate/mfa/revoke). |
i18n | Locale + message catalogs. |
The request lifecycle
const ctx = await auth.resolveSession(req, {
expectedTenantId: 'org-123', // optional cross-tenant guard
requestSnapshot: { ip, userAgent }, // optional, drives anomaly detectors
})
if (!ctx) {
return new Response('Unauthorized', { status: 401 })
}
// ctx.session, ctx.identity, ctx.anomaly? <- all typed
If anomaly detectors are wired and requestSnapshot is provided,
ctx.anomaly.decision is one of allow / deny / step-up.
Strict mode
AuthEngine.strict() is a boot-time validator. Call it after construction
to catch misconfigurations early.
auth.strict({ env: 'development' }) // warns only
auth.strict({ env: 'production' }) // throws AUTH/MISCONFIGURED
In production, strict rejects:
CookieTransport.secure: falseAuthMemoryAdapter(the in-memory dev store)AuthMemoryLimiter(the in-memory dev limiter) or no limiter at all- No listeners attached to the
lockoutevent - Insecure JWT keys (short HS256 secret, missing kid)
- Compliance preset violations (HIPAA/FIPS require Argon2 + dataAtRest)
- A registered provider with no callback URL when
baseUrlis set
See Production hardening for the full list of gates.
Typed generics
AuthEngine<Profile, Tenant, OrgMeta> is parameterised by three shapes:
type Profile = { displayName: string; avatarUrl?: string }
type Tenant = { region: 'eu' | 'us'; tier: 'free' | 'pro' }
type OrgMeta = { plan: 'team' | 'enterprise' }
export const auth = new AuthEngine<Profile, Tenant, OrgMeta>({
/* config */
})
The generics propagate everywhere: auth.identities.findById(id) returns
Identity<Profile>, auth.orgs.list(identityId) returns
Membership<OrgMeta>[], and provider hooks see Profile on every payload.
Where each facet lives
- Sessions -> - the 7-row rotation matrix.
- Identities -> - CRUD, linking, GDPR.
- Passwords -> -
AuthScryptHasher,AuthArgon2idHasher. - Transports -> - Cookie, Bearer, JWT, Composite, DPoP.
- Events -> -
AuthInMemoryEvents,AuthRedisEvents,AuthWebhookDeliverer. - Anomaly -> - detectors and the step-up decision.
- Compliance -> -
gdpr,hipaa,soc2,fips. - Errors -> - every
AUTH/*code and its status.