Skip to main content

Installation

WIP

Install duck-auth, scaffold a starter, and run the 60-second quickstart with the Memory adapter.

Prerequisites

  • Node.js 18+.
  • TypeScript 5.0+. duck-auth uses const type parameters and the satisfies operator.
  • npm, bun, pnpm, or yarn.

Install duck-auth

The core engine and every batteries-included primitive (transports, providers, adapters, channels, server adapters, clients) ship in one package. Optional features (Argon2, passkeys, Redis, SMTP, SAML, OpenTelemetry) are peer-dependencies that load only when wired, so the bundle stays small.

Install the package

# npm
npm install @gentleduck/auth

# bun
bun add @gentleduck/auth

# pnpm
pnpm add @gentleduck/auth

# yarn
yarn add @gentleduck/auth

Pick your optional peers

Install only what you wire:

PeerWhen you need it
@node-rs/argon2Argon2id password hashing (HIPAA / FIPS presets require it).
@simplewebauthn/serverPasskey provider (discoverable + username flows).
ioredis or @upstash/redisRedis-backed session / idempotency / limiter / events / DPoP nonce stores.
nodemailer (or compatible)SMTP channel.
@aws-sdk/client-sesAWS SES channel.
twilioTwilio SMS channel.
web-pushWebPush channel.
@opentelemetry/apiOpenTelemetry instrumentation.
@node-saml/node-samlSAML provider.

60-second quickstart (Memory adapter)

src/lib/auth.ts
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, name: 'duck-sid' }),
  stores: {
    identities: adapter.identities,
    sessions: adapter.sessions,
    credentials: adapter.credentials,
  },
  events: new AuthInMemoryEvents(),
  limiter: new AuthMemoryLimiter({ max: 5, windowMs: 60_000 }),
  passwords: { hasher: new AuthScryptHasher() },
})

Mount on your framework

src/server.ts (Express)
import express from 'express'
import { authMountSignIn, authMountSignOut, authMountSession } from '@gentleduck/auth/server/express'
import { auth } from './lib/auth'

const app = express()
app.use(express.json())

app.post('/auth/signin', authMountSignIn(auth))
app.post('/auth/signout', authMountSignOut(auth))
app.get('/auth/session', authMountSession(auth))

app.listen(3000)

Validate at boot

auth.strict({ env: 'development' })  // dev: only warns
auth.strict({ env: 'production' })   // prod: throws on insecure config

Scaffold via the CLI

If you prefer a scaffolded starter, the CLI emits a complete auth.ts plus a .env template.

# Quickstart: Memory adapter, AuthScryptHasher, CookieTransport
bunx @gentleduck/auth init src/lib

# Production: Redis adapter, Argon2id, JwtTransport with rotated keys
bunx @gentleduck/auth init src/lib --production

Then run the doctor at boot to catch misconfigurations:

bunx @gentleduck/auth doctor

Mint signing keys

If you use JwtTransport, you need a current signer plus at least one retired verify key for in-flight rotation.

# Symmetric HS256 (simplest, single secret)
bunx @gentleduck/auth keys generate hs256

# Asymmetric ES256 (for DPoP, public JWKS endpoint, or multi-tenant)
bunx @gentleduck/auth keys generate ec256

# Rotate: emits new secret + the snippet to keep the prior kid on verifyKeys
bunx @gentleduck/auth keys rotate hs256

See Transports -> JWT for the full rotation lifecycle.

Emit SQL migrations

If you use the SQL bridge (@gentleduck/auth/adapters/sql), the CLI emits ready-to-run DDL for the three core tables (identities, credentials, sessions):

bunx @gentleduck/auth migrate pg      # PostgreSQL
bunx @gentleduck/auth migrate mysql   # MySQL
bunx @gentleduck/auth migrate sqlite  # SQLite

The DDL matches the bundled Drizzle reference schemas in @gentleduck/auth/adapters/drizzle/{pg,mysql,sqlite}.

Emit OpenAPI

Once you have wired providers, the CLI can introspect your auth.ts and emit an OpenAPI 3.1 spec for the mounted routes:

bunx @gentleduck/auth emit-openapi > openapi.yaml

Next steps