@cairs-npm/express-auth-2fa
v0.1.4
Published
Express + MongoDB module for password sign-in with 2-Step Verification (email, SMS, authenticator app, passkey), verification-method management, sessions and audit logging
Downloads
640
Readme
@cairs-npm/express-auth-2fa
Private Express + MongoDB module that adds password sign-in with 2-Step Verification (TOTP authenticator apps + SMS codes), verification-method management, session handling and a security audit log to any Express app.
Install & mount
import express from "express";
import { MongoClient } from "mongodb";
import { createAuthRouter, migrate, twilioSms } from "@cairs-npm/express-auth-2fa";
const db = new MongoClient(process.env.MONGO_URI!).db("app");
await migrate(db); // creates auth_* collections, validators, indexes, TTLs
const app = express();
app.use(express.json());
app.use(
createAuthRouter({
db,
issuer: "YourApp",
jwtPrivateKey: process.env.JWT_PRIVATE_KEY!, // PKCS8 PEM, Ed25519 or RSA
encryptionKey: process.env.AUTH_ENC_KEY!, // 32 bytes (raw or base64)
userAdapter: {
findByLogin: (login) => db.collection("users").findOne({ email: login }),
findById: (id) => db.collection("users").findOne({ _id: id }),
},
smsProvider: twilioSms({ sid, token, from }), // or consoleSms() in dev
}),
);One install, one line to mount. All endpoints live under /auth/* and
/account/security*, implementing the shared @cairs-npm/auth-2fa-contract.
Config
| Field | Required | Default | Notes |
| --- | --- | --- | --- |
| db | yes | — | mongodb Db; the module owns auth_* collections only |
| issuer | yes | — | JWT issuer + otpauth issuer |
| jwtPrivateKey | yes | — | PKCS8 PEM; Ed25519 → EdDSA, RSA → RS256 |
| encryptionKey | yes | — | 32-byte AES-256-GCM key (raw string or base64) |
| keyId | no | "v1" | written on each encrypted field for rotation |
| userAdapter | yes | — | findByLogin, findById |
| smsProvider | yes | — | { send(phoneE164, message) } |
| logger | no | noop | pino-compatible; output is redacted (NFR-B1) |
| hooks.onSecurityEvent | no | — | called for every audit event (BR-20) |
| clock | no | Date.now | inject for tests |
| collectionPrefix | no | "auth_" | |
| limits | no | spec | lockout, SMS resend/caps, per-IP rate limits |
| argon2 | no | 64MB/3it/4p | Argon2id params |
| cookies | no | secure/lax | refresh + trusted-device + CSRF cookies |
| ttl | no | spec | access 15m, refresh/trusted 30d, challenge/stepUp 5m |
| exposeDocs | no | false | serves GET /auth/openapi.json |
Data model
Six+ collections (auth_credentials, auth_mfa_methods, auth_challenges,
auth_sessions, auth_trusted_devices, auth_events, auth_rate_limits),
created by migrate(db) with JSON-schema validators, indexes and TTLs.
TOTP secrets and phone numbers are AES-256-GCM encrypted; SMS codes, refresh
tokens and trusted-device tokens are stored as SHA-256 hashes only.
Security highlights
- Argon2id passwords; equal
INVALID_CREDENTIALStiming for unknown users. - Lockout after 5 failed passwords; challenge lockout after 5 wrong codes.
- TOTP RFC 6238 (±1 step) with per-method replay guard.
- Rotating refresh tokens; reuse of a rotated token revokes the whole family.
- CSRF double-submit token on the cookie-authenticated refresh route.
- Per-IP and per-user rate limits backed by MongoDB (stateless across instances).
- One error shape:
{ error, message, attemptsLeft?, retryAt? }.
Development
npm install
npm test # vitest + mongodb-memory-server (replica set)
npm run build # tsup → ESM + CJS + d.ts
npm run typecheck && npm run lintexamples/express-app is a runnable dev server for E2E testing.
