Installation
WIPInstall @gentleduck/auth, pick the subpaths your stack needs with their peer dependencies, and sign a first user in
@gentleduck/auth is one package with 53 export paths. The engine and its types come from core; every store, limiter, provider, server adapter and client binding lives behind its own subpath, so you only bundle what you import.
Requirements
| Requirement | Version | Why |
|---|---|---|
| Node.js | 22 or newer | Declared in engines. Bun runs it too. |
| TypeScript | 5.0 or newer | createAuth() uses const type parameters. |
| Module resolution | bundler or nodenext | Subpaths are declared through the exports map only. |
The package has one runtime dependency, @gentleduck/error, which AuthError extends.
Install
npm i @gentleduck/auth
That covers the engine, the memory store and limiter, the cookie, bearer, JWT and DPoP transports, passwords with scrypt, magic links, TOTP, API keys, OAuth, and every server adapter but NestJS.
Optional peer dependencies
Every peer is optional. Install one only when you import the subpath that needs it.
| Peer | Needed by |
|---|---|
drizzle-orm | adapters/drizzle/{pg,mysql,sqlite} |
pg, mysql2 or better-sqlite3 | The driver for the Drizzle dialect you pick. |
@node-rs/argon2 | passwords(), whose default hasher is Argon2id. Loaded on first hash; passwords({ hasher: scryptHasher() }) needs no peer. |
@simplewebauthn/server | providers/passkey, and WebAuthn MFA in providers/mfa. Loaded on first ceremony. |
@aws-sdk/client-kms | authAwsKmsProvider in core/dataAtRest. Loaded on first use. |
@nestjs/common | server/nestjs |
Two integrations take a client you build rather than a peer:
- Redis and Valkey stores accept any client with the
RedisLike.Clientshape.@upstash/redisfits as is; wrapioredisoriovalkeywithvalkeyAdapterfromadapters/valkey. See Redis. - SAML takes a built
@node-saml/node-samlinstance. See SAML.
Your first sign-in
Build the engine
createAuth() needs a public baseUrl and a store for identities, sessions and credentials. MemoryAdapter provides all three in process, which is right for tests and a first run.
import { MemoryAdapter } from '@gentleduck/auth/adapters/memory'
import { createAuth } from '@gentleduck/auth/core'
import { cookieTransport } from '@gentleduck/auth/core/transport'
import { passwords } from '@gentleduck/auth/providers/passwords'
export const auth = createAuth({
baseUrl: 'http://localhost:3000',
stores: new MemoryAdapter(),
transport: cookieTransport({ name: 'sid', secure: false }),
providers: [passwords()],
})
secure: false lets the cookie travel over plain http on localhost. createAuth() runs strict() for the current NODE_ENV, and under production it refuses an insecure cookie, the memory store and the memory limiter; see Production hardening.
Create an account
const ada = await auth.identities.create({ profile: { email: 'ada@example.com', name: 'Ada', username: 'ada' } })
await auth.passwords.set(ada.id, 'correct-horse-battery', auth.cfg.stores.credentials)
Sign in
flows.signIn runs the provider, opens a session and answers the intents a server adapter turns into a response. sid is the plaintext session id the cookie carries.
const { sid, intents } = await auth.flows.signIn({
providerId: 'password',
input: { email: 'ada@example.com', password: 'correct-horse-battery' },
})
intents.map((i) => i.type) // ['setCookie', 'setCookie']: the session, and its CSRF companion
Read the session back
const resolved = await auth.resolveSession({ headers: new Headers({ cookie: `sid=${sid}` }) }).orNull()
resolved?.identity?.id === ada.id // true
orNull() answers null for a request with no session or a revoked one, and still throws anything else.
Next steps
- Quick start: the same engine behind Express, with sign-up and sign-out.
- Adapters: swap
MemoryAdapterfor Postgres, MySQL or SQLite. - Server adapters: write the routes on your framework.