ModularAuth-Kit is a drop-in authentication module for Express.js + MongoDB + TypeScript projects. Copy the src/auth/ folder into your project, call createAuthModule(config), and you get a fully functional auth system with 16 endpoints.
No. All optional features are disabled by default. You start with just register, login, logout, profile, and change-password. Enable features one by one as you need them.
Five lines of config and you get 7 endpoints:
const config = createConfig({
session: { secure: false }, // false for HTTP dev
});
app.use('/auth', createAuthModule(config));Yes! We provide a ready-made prompt that AI agents can follow to integrate ModularAuth-Kit into your project automatically. See AI-Assisted Integration.
ModularAuth-Kit is built for Express.js + MongoDB + TypeScript. Here's the full compatibility:
| Platform | Works? | Notes |
|---|---|---|
| Express.js 4.x / 5.x | ✅ Yes | Built for this |
| NestJS | Can mount as raw Express middleware, but fights NestJS patterns | |
| Next.js (API Routes) | ❌ No | Different request/response model |
| Next.js (custom Express server) | Uncommon setup, but possible | |
| Fastify / Hono / Koa | ❌ No | Different middleware APIs |
| Python / Go / Other | ❌ No | TypeScript + Node.js only |
Why? The module uses Express Router, (req, res, next) middleware, cookie-parser, and Mongoose. These are Express-specific APIs.
Not out of the box. The module uses Mongoose (MongoDB). However, the repository pattern makes it possible to swap databases — you'd implement the repository interfaces (IUserRepository, ISessionRepository, etc.) with your SQL queries. See Custom Database.
You haven't set the SESSION_SECRET environment variable. Add one line to your existing .env:
SESSION_SECRET=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")This is the only required env var. You don't need to set MONGODB_URI again — the auth module reuses your existing Mongoose connection.
No. If your project already calls mongoose.connect(), the auth module automatically uses that connection. No connectDatabase() call, no MONGODB_URI duplication. Just mount the module and it works.
Set session.secure to false:
createConfig({
session: { secure: false },
});In production, always set secure: true and use HTTPS.
You enabled OAuth (login.allowGoogleOAuth: true) but haven't set the Google credentials. Either:
- Add
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_CALLBACK_URLto.env - Or disable OAuth:
login: { allowGoogleOAuth: false }
Change the mount path in your Express app:
// Default
app.use('/auth', createAuthModule(config));
// Custom
app.use('/api/v1/auth', createAuthModule(config));| Setting | Default | Description |
|---|---|---|
maxAge |
7 days | Absolute session lifetime |
idleTimeout |
30 minutes | Expires after inactivity |
Sessions expire at whichever comes first.
This is intentional security. If an attacker tries to log in with victim@example.com, different error messages would reveal whether that email is registered. By using the same message ("The email or password you entered is incorrect"), we prevent email enumeration attacks.
Yes. Enable username in registration and add it to login identifiers:
createConfig({
registration: {
fields: { username: { enabled: true, required: true } },
},
login: {
identifiers: ['email', 'username'], // Accept either
},
});- OAuth-only users (no password) are blocked with a clear error
- Current password is verified
- New password must be different from the current one
- New password is hashed with argon2id
- All other sessions are revoked — the current session stays active (you stay logged in)
- The event is logged in login history (if enabled)
| Rule | Default |
|---|---|
| Min length | 8 characters |
| Max length | 128 characters |
| Uppercase required | Yes |
| Lowercase required | Yes |
| Number required | Yes |
| Special character | No |
Customizable via registration.validation.password.
After N failed login attempts (default 5), the account is temporarily locked for M minutes (default 15). During lockout:
- Login returns HTTP 423 with
ACCOUNT_LOCKED - The lockout auto-expires — no admin action needed
- Successful login resets the failure counter
Sessions are stored server-side in MongoDB (in the sessions collection). The browser only receives an httpOnly cookie containing the session ID — no user data is stored in the cookie.
Yes. Each login creates a new session. The default limit is 5 concurrent sessions per user. When the limit is exceeded, the oldest session is automatically revoked.
If session management is enabled:
# List all sessions
GET /auth/sessions
# Revoke a specific session (by its ID)
DELETE /auth/sessions/:sessionIdUsers can only revoke their own sessions. They cannot revoke their current session (use /auth/logout instead).
When the server restarts, it reconnects to MongoDB. Your sessions are still in the database and should work. If they don't:
- Check that your cookie jar still has the
sidcookie - Check that
SESSION_SECREThasn't changed (changing it invalidates all signed cookies)
argon2id — the algorithm recommended by OWASP for password hashing. It's memory-hard (resistant to GPU attacks) and provides side-channel resistance.
Parameters: Memory 19 MiB, Iterations 2, Parallelism 1, Hash length 32 bytes.
No. Tokens are hashed with SHA-256 before storage. The raw token is sent to the user via email; only the hash exists in the database. A database breach won't expose valid tokens.
ModularAuth-Kit uses the double-submit cookie pattern. A CSRF token is set in a cookie and must be included in request headers. Combined with SameSite=lax cookies, this prevents cross-site request forgery.
Yes. Rate limits are applied per-endpoint:
| Endpoint | Window | Max Attempts |
|---|---|---|
| Login | 15 minutes | 10 |
| Register | 1 hour | 5 |
| Forgot Password | 15 minutes | 3 |
| Change Password | 15 minutes | 5 |
No. The default email adapter is console, which prints emails to your terminal. Use this for development:
createConfig({
email: { adapter: 'console' }, // Default — prints to terminal
});For production, switch to nodemailer with SMTP credentials.
- User registers → 6-digit OTP is sent to their email
- User calls
POST /auth/verify-emailwith the OTP - If
requiredToLoginistrue, unverified users can't access protected routes
They can request a new one:
POST /auth/resend-verificationThe old OTP is invalidated and a new one is sent.
No. ModularAuth-Kit implements Google OAuth 2.0 directly using HTTP requests with PKCE. No Passport.js dependency.
If they registered via Google OAuth, they won't have a password set. They'll need to use the forgot-password flow to set one, or continue using Google login.
The accounts are linked automatically. The Google ID is attached to the existing user, and they can log in with either method.
Yes. See Adding Custom Fields. You'll need to:
- Update the Mongoose schema
- Update the TypeScript types
- Update the Zod validation schema
Not out of the box, but the repository pattern makes it possible. Implement the repository interfaces (IUserRepository, ISessionRepository, etc.) with your PostgreSQL queries. See Custom Database.
Yes. Implement the IEmailAdapter interface with your SendGrid client. See Custom Email Provider.
See Extending Middleware. You can add middleware before the auth router mount:
app.use('/auth', customMiddleware, createAuthModule(config));Your TypeScript is not compiling with ESM module resolution. Ensure tsconfig.json has:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node"
}
}This happens if a login history event is being recorded without proper request metadata. Ensure you're passing the request's User-Agent header to service methods. This was a known issue fixed in Phase 19.
Check:
- You're using
-c cookies.txtAND-b cookies.txtwith curl - If using a frontend: credentials mode is
includein fetch/axios - If cross-origin:
SameSitemust benoneandsecuremust betrue
You've hit the rate limit. Wait for the rate limit window to expire:
- Login: 15 minutes
- Register: 1 hour
- Forgot password: 15 minutes
- Change password: 15 minutes
For development, you can restart the server to reset in-memory rate limit counters.