FAQ
WIPAnswers to the questions every team asks before adopting duck-auth.
Should I pick duck-auth?
If you have a TypeScript backend that needs to support more than one runtime / framework, want batteries included without per-MAU pricing, and can run the auth runtime yourself - yes. If you're 100% Next.js, NextAuth is also fine. If you don't want to run anything, Clerk / WorkOS / Stytch are the right pick.
See the Comparison page for details.
Is it production-ready?
The 0.1 surface covers ~98% of the v1.0 MUST features and 617 tests pass. Several teams run it in production. You should:
- Pin a version (no
^0.1.0ranges pre-1.0). - Run
auth.strict({ env: 'production' })at boot. - Apply a compliance preset that matches your scope.
- Audit the
SECURITY.mddocument. - Wire OpenTelemetry and a webhook deliverer for audit log fan-out.
What about cost?
duck-auth itself is MIT-licensed and free. Your costs are:
- Whatever you spend on Redis (~$50-100/mo for production).
- Your Postgres / MySQL / SQLite (whatever you'd run anyway).
- Your email / SMS provider (Resend / SES / Twilio).
Compared to Clerk at 0.02 per MAU after 5k, duck-auth wins above ~6k MAUs and significantly wins above 50k.
Can I migrate from NextAuth / Better-Auth / Clerk?
Yes. The migration path is:
- Export your users from the old system (most have an export endpoint).
- Use
auth.identities.bulkCreatewith pre-hashed passwords - duck-auth does not re-hash, so bcrypt / Argon2 / PBKDF2 hashes import verbatim. - Combine with
passwords.autoRehash: true, and the next sign-in silently upgrades each hash to your chosen format. - Wire your OAuth providers with the same
redirectUris. - Once verified, point your DNS / load balancer at the new service.
No big-bang migration; old hashes coexist with new hashes for as long as you need.
Can I share users across multiple apps?
Yes. Two patterns:
- Same store, multiple AuthRoots: each app constructs its own
AuthEngineagainst the shared Postgres + Redis. Identities are shared; sessions are app-specific. - One AuthEngine, many domains: deploy a single auth service at
auth.example.comand useCookieTransportwithdomain: '.example.com'so the cookie is shared acrossapp1.example.com,app2.example.com.
For OIDC-based federation between services, use JwtTransport and
expose your /.well-known/openid-configuration - downstream apps
verify JWTs locally.
Can I have my own provider?
Yes. Implement AuthProvider.IProvider:
const myProvider: AuthProvider.IProvider<IBegin, IComplete> = {
id: 'my-provider',
async begin(input, ctx) { /* ... */ },
async complete(input, ctx) { /* ... */ },
}
auth.providers.use(myProvider)
See Writing a custom provider ->.
Can I have my own adapter?
Yes. Implement SqlBridge.IBridge and pass it to authCreateSqlStores:
const stores = authCreateSqlStores({ bridge: myBridge })
Or implement the four store interfaces directly. Run the [compliance suite](/duck-auth/adapters#the-compliance-suite) against your implementation.
Can I run on serverless / edge?
Yes, with caveats:
- Vercel Edge / Cloudflare Workers: use
JwtTransport(no DB hits per request) andAuthArgon2idHasher(Node'scrypto.scryptisn't on Workers). - AWS Lambda: works as-is. Cold-start cost is ~300ms for the first request after a deploy - pin a warmer if that matters.
- Cloudflare Workers with cookies: works, but keep one auth instance per worker and share via durable-object if you need cross-replica state.
How fast is it?
A signin endpoint with AuthScryptHasher defaults is ~30ms per call
(20ms hash + 5ms DB + 5ms cookie issue). With Argon2id at OWASP
defaults, ~80ms. JWT verify (no DB hit) is <1ms.
The bottleneck is always the hasher - that's by design. Don't lower the cost; raise the budget.
Does it support row-level security?
duck-auth doesn't enforce RLS - that's duck-iam's job. The recommended pattern is to project the auth-side identity into an iam-side subject and run the iam engine against the row's attributes:
const subject = projectToSubject(ctx.identity, ctx.session)
const allowed = iam.can({ subject, action: 'read', resource: { type: 'post', attributes: post } })
What about WebAuthn / passkeys on iOS / Android?
duck-auth's passkey provider uses WebAuthn - which Safari (iOS), Chrome (Android), and every desktop browser supports. For native iOS / Android apps, integrate via the OS-level Credential Manager APIs and POST the attestation/assertion JSON to your duck-auth callback.
A reference client SDK for iOS is on the v1.1 roadmap.
Does it support OAuth2 client-credentials (M2M)?
Yes. Register the m2m facet and use it on top of apiKeys:
const cred = await auth.m2m.createClient({
name: 'CI deployment bot',
scopes: ['deploy:write'],
})
// CI exchanges:
POST /auth/m2m/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=...&client_secret=...&scope=deploy:write
// -> 200 { access_token, token_type, expires_in, scope }
The runtime issues a short-lived JWT for the M2M flow.
What's the relationship with @gentleduck/iam?
duck-auth answers "who is this caller?"; duck-iam answers "what may they do?" Two libraries, one bridge function. See the bridging guide.
How do I report a security issue?
Email security@gentleduck.dev (PGP key on the SECURITY.md).
Do not open a public GitHub issue.