npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gentleduck/auth

v5.0.0

Published

Authentication for modern TypeScript apps. Faceted, framework-agnostic, transport-pluggable. Pairs with @gentleduck/iam.

Downloads

836

Readme


Every TypeScript auth library makes you choose framework lock-in (NextAuth, Auth.js), a hosted control plane (Clerk, WorkOS, Stytch), or DIY-on-Lucia + passport + your own glue. @gentleduck/auth is the third option, but unified: framework-agnostic core, batteries-included adapters, no hosted plane. Wire it into Express, Hono, Next.js, Fastify, Koa, NestJS, Elysia, gRPC, or your own router with one adapter import.

Zero hosted dependencies. Tree-shakeable subpath exports. Lazy peer deps for the heavy bits (argon2, simplewebauthn, ioredis, nodemailer).

Install

npm install @gentleduck/auth
# or
bun add @gentleduck/auth

Optional peer dependencies (install only what you wire):

| Peer | When you need it | |---|---| | @node-rs/argon2 | Argon2id password hashing (FIPS / HIPAA presets) | | @simplewebauthn/server | Passkey / WebAuthn-MFA | | ioredis or @upstash/redis | Redis-backed session / idempotency / limiter / events / DPoP-nonce stores | | nodemailer (or compatible) | SMTP channel | | drizzle-orm + driver | Drizzle adapter (pg / mysql / sqlite) | | @prisma/client | Prisma adapter | | node-saml | SAML 2.0 SP |

Quick start

import { defineAuth } from '@gentleduck/auth/core'
import { MemoryAuthAdapter } from '@gentleduck/auth/adapters/memory'
import { MemoryLimiter } from '@gentleduck/auth/limiters/memory'
import { password } from '@gentleduck/auth/providers/password'

const storage = new MemoryAuthAdapter()

export const auth = defineAuth({
  baseUrl: 'http://localhost:3000',
  storage,
  limiter: new MemoryLimiter({ max: 5, windowMs: 60_000 }),
  providers: [
    (a) => password({
      findIdentityByEmail: (email) => storage.identities.findByEmail(email, {}),
      passwords: a.passwords,
    }),
  ],
})

const identity = await auth.identities.create({ profile: { email: '[email protected]' } })
await auth.passwords.set(identity.id, 'correct-horse-battery')

const result = await auth.flows.signIn({
  providerId: 'password',
  input: { email: '[email protected]', password: 'correct-horse-battery' },
})
// result.session, result.sid, result.intents[]

defineAuth is the factory that wires the 14 facets, picks sane defaults (CookieTransport, ScryptHasher, InMemoryEvents), and registers the providers you pass. For full control, instantiate AuthEngine directly - both APIs accept the same primitives.

Or scaffold it via the CLI

bunx @gentleduck/auth init src/auth                # quickstart
bunx @gentleduck/auth init src/auth --production   # Redis + JWT + Argon2id
bunx @gentleduck/auth doctor                       # run AuthEngine.strict()
bunx @gentleduck/auth keys generate hs256          # mint a JWT signing secret
bunx @gentleduck/auth keys generate ec256          # mint an ES256 keypair (DPoP)

Architecture

AuthEngine is the 14-facet root: every state-changing operation lives behind one named facet so adapters, transports, and providers compose without back-channel coupling.

| Facet | Owns | |---|---| | identities | profile CRUD, link/unlink, soft-delete + grace-period restore, GDPR export, bulk import | | sessions | rotateOrCreate (single privilege-changing API), getBySid, revoke, revokeAllForIdentity, gc | | credentials | password / api-key / oauth / passkey / recovery / totp / webauthn-mfa rows; CAS rotation | | passwords | strength + cap validation, constant-time verify, needsRehash + auto-rehash, common-list reject | | mfa | TOTP enrollment + verify, backup-code mint/verify, WebAuthn-MFA, AAL3 detection | | apiKeys | mint / list / rotate / revoke / verify + scope checks, tenant-bound issuance | | flows | signIn / signOut / signUp (multi-stage) / password-reset / email-verification / account-deletion / linkProvider / unlinkProvider / impersonate / step-up / step-down | | csrf | double-submit + origin-only + sec-fetch-site gates, __Host- cookie | | idempotency | per-(identity, key) tombstone + poll, NaN-bypass defense on TTL | | webhooks | HMAC + timestamp + tolerance, retry w/ backoff, dead-letter, SSRF-guarded URLs | | events | typed bus, lockout / signin.success / signin.failed / suspicious / session.revoked / mfa.removed | | hijack | IP / UA drift detection + step-up / rotate / revoke reaction policy | | anomaly | pluggable detectors (impossible-travel, device-fingerprint), composition + decision ladder | | orgs | org + membership CRUD, role sanitisation, multi-tenant guard |

Plus m2m (client_credentials OAuth2 grant), compliance (FIPS / HIPAA / SOC2 presets), plugin (named install + facet extension), and audit (admin-mutation hook with redaction).

Providers

| Path | What | |---|---| | @gentleduck/auth/providers/password | Email + password | | @gentleduck/auth/providers/magic-link | Passwordless one-time link | | @gentleduck/auth/providers/passkey | WebAuthn passkey (lazy peerDep on @simplewebauthn/server) | | @gentleduck/auth/providers/api-key | Long-lived bearer keys via ApiKeysFacet | | @gentleduck/auth/providers/oauth/google | Google OAuth (PKCE + nonce) | | @gentleduck/auth/providers/oauth/github | GitHub OAuth (PKCE + state) | | @gentleduck/auth/providers/oauth/microsoft | Microsoft / Entra ID OAuth | | @gentleduck/auth/providers/oauth/discord | Discord OAuth | | @gentleduck/auth/providers/oauth/linkedin | LinkedIn OAuth | | @gentleduck/auth/providers/oauth/apple | Sign in with Apple | | @gentleduck/auth/providers/oauth/core | Generic OAuth2 / OIDC client base. Build your own per-IdP wrapper | | @gentleduck/auth/providers/saml | Wrapper over @node-saml/node-saml (lazy peerDep): SP-initiated + IdP-initiated SSO, SP metadata XML generation, Single Logout (SP- and IdP-initiated) |

Transports

import {
  CookieTransport,    // __Host- prefix + HttpOnly + SameSite=Lax (default)
  BearerTransport,    // opaque tokens in Authorization header
  JwtTransport,       // HS256 / RS256 / ES256 / EdDSA + JWKS rotation
  CompositeTransport, // chain multiple transports
} from '@gentleduck/auth/core/transport'

import {
  DPoPVerifier,
  MemoryDPoPNonceStore,
  computeJwkThumbprint,
  bindPayloadToDPoP,
} from '@gentleduck/auth/core/transport/dpop' // RFC 9449

Storage adapters

import { MemoryAuthAdapter } from '@gentleduck/auth/adapters/memory'
import { drizzlePgStorage }  from '@gentleduck/auth/adapters/drizzle/pg'
import { drizzleMysqlStorage } from '@gentleduck/auth/adapters/drizzle/mysql'
import { drizzleSqliteStorage } from '@gentleduck/auth/adapters/drizzle/sqlite'
import { createSqlAuthStores } from '@gentleduck/auth/adapters/sql' // build your own bridge
import {
  RedisSessionStore,
  RedisIdempotencyStore,
  RedisLimiter,
  RedisEvents,
  RedisDPoPNonceStore,
  FakeRedis, // in-tree, for tests
} from '@gentleduck/auth/adapters/redis'

Server adapters

// Express
import { mountSignIn, mountSignOut, mountProviderBegin } from '@gentleduck/auth/server/express'
app.post('/auth/signin', mountSignIn(auth))

// Hono
import { mount } from '@gentleduck/auth/server/hono'
mount(app, auth, { prefix: '/auth' })

// Next.js App Router
import { nextSignIn, nextSignOut } from '@gentleduck/auth/server/next'
export const POST = nextSignIn(auth)

// Fastify, Koa, NestJS, Elysia, gRPC
import { fastifySignIn } from '@gentleduck/auth/server/fastify'
import { koaSignIn }     from '@gentleduck/auth/server/koa'
import { nestSignIn }    from '@gentleduck/auth/server/nestjs'
import { elysiaSignIn }  from '@gentleduck/auth/server/elysia'
import { authGrpcService } from '@gentleduck/auth/server/grpc'

// Generic Web-Fetch executor (Cloudflare Workers, Bun, Deno)
import { executeIntents, parseSignInBody } from '@gentleduck/auth/server/generic'

Channels

| Path | What | |---|---| | @gentleduck/auth/channels/console | Console / Noop / Test channels (dev + test) | | @gentleduck/auth/channels/smtp | Nodemailer-compatible SMTP relay | | @gentleduck/auth/channels/resend | Resend HTTP API | | @gentleduck/auth/channels/twilio | Twilio SMS | | @gentleduck/auth/channels/webpush | Web Push (web-push) | | @gentleduck/auth/channels/ses | AWS SES (@aws-sdk/client-sesv2) |

Client libraries

// React - <AuthProvider> + useSession / useSignIn / useSignOut
import { createAuthClient } from '@gentleduck/auth/client/react'

// Vue, Solid, Svelte - parallel APIs
import { createAuthClient as createVueAuth }    from '@gentleduck/auth/client/vue'
import { createAuthClient as createSolidAuth }  from '@gentleduck/auth/client/solid'
import { createAuthClient as createSvelteAuth } from '@gentleduck/auth/client/svelte'

// Vanilla - promise-based signIn / signOut / resolveSession
import { createAuthClient } from '@gentleduck/auth/client/vanilla'

Captcha verifiers

import { turnstileVerifier } from '@gentleduck/auth/captcha/turnstile'
import { hcaptchaVerifier }  from '@gentleduck/auth/captcha/hcaptcha'
import { recaptchaVerifier } from '@gentleduck/auth/captcha/recaptcha'

Tooling

| Path | What | |---|---| | @gentleduck/auth/cli | duck-auth init / doctor / keys generate | | @gentleduck/auth/openapi | buildOpenApiSpec + renderOpenApiYaml for the auth surface | | @gentleduck/auth/oidc | OIDC discovery-doc + JWKS helper | | @gentleduck/auth/oidc/op | Full OAuth2/OIDC OP: /authorize (code + S256 PKCE), /token (auth_code + refresh, family-rotated), /userinfo, /introspect, /revoke, /register (RFC 7591 Dynamic Client Registration) | | @gentleduck/auth/oidc/op/drizzle/pg | Postgres Drizzle stores for the OIDC OP (5 tables, GC helper) | | @gentleduck/auth/oidc/op/drizzle/sqlite | SQLite Drizzle stores for the OIDC OP | | @gentleduck/auth/oidc/op/drizzle/mysql | MySQL Drizzle stores for the OIDC OP | | @gentleduck/auth/i18n | Message catalogue + Lingui adapter | | @gentleduck/auth/telemetry | OpenTelemetry metrics instrumentation |

Production primitives

  • AuthEngine.strict({ env: 'production' }) - boot-time validation: rejects secure: false cookie transport, NoopLimiter, memory stores, missing lockout listener, non-HTTPS baseUrl
  • JwtTransport.rotateSignKey() + retireVerifyKey(kid) - zero-downtime JWKS rotation with overlap window
  • auth.compliance.applyPreset('soc2' | 'hipaa' | 'fips') - tightens password / session / MFA / data-at-rest settings to the named regulatory floor
  • auth.webhooks - HMAC body + timestamp + freshness tolerance, exponential backoff, dead-letter sink, SSRF guard on endpoint URLs, redirect: 'error' on dispatch
  • auth.hijack + auth.anomaly - drift detection, decision ladder (allow / step-up / deny), pluggable signals
  • auth.idempotency - per-(identity, key) tombstone + poll for replay-safe mutating routes
  • Refresh-token reuse detection (RFC 6749 §10.4) on OAuth refresh families
  • DPoP (RFC 9449) - proof-of-possession on bearer tokens with ath binding and server nonce
  • Tenant boundary: every adapter respects ctx.tenantId; M2M + api-key providers refuse cross-tenant identification

Security posture

AuthEngine.strict() runs every production-grade gate before boot.

See SECURITY.md for the STRIDE / OWASP ASVS mapping of every threat the library mitigates and every threat the host app must own.

Module sizes (gzipped)

| Module | Size | |--------|------| | Core AuthEngine (typical import) | ~22 KB | | Each transport | 2 - 6 KB | | Each provider | 1.5 - 8 KB | | Each adapter | 2 - 9 KB | | Each server middleware | 2 - 4 KB | | Each client library | 1.5 - 2.5 KB | | Each channel | 1 - 3 KB | | CLI | 12 KB (binary, not imported by app) |

Real deployments importing only what they wire end up at 25 - 60 KB total. The "import everything" worst case (import * from '@gentleduck/auth') is not the intended usage.

Docs

Contributing

PR checklist + style notes in the repo's CONTRIBUTING.md. Security disclosures: SECURITY.md.

License

MIT. See LICENSE.