A secure, enterprise-grade authentication backend boilerplate built with Node.js, Express, MongoDB (Mongoose), Argon2id, RFC 6238 TOTP 2FA, RFC 6819 Token Theft Detection, AES-256-GCM Cryptography, Zod, Helmet, Express-Rate-Limit, Passport (Google & GitHub OAuth), JWT, and Nodemailer (Google OAuth2).
๐ Architecture & Threat Model: For the complete 9-layer defense-in-depth security architecture, sequence flowcharts, NIST SP 800-63B AAL2 matrix, and OWASP Top 10 mitigation tables, see SECURITY.md.
This system implements modern security best practices:
- Memory-Hard Password Storage (Argon2id): 19MB memory-hard hashing with zero-downtime lazy-rehash from legacy Bcrypt.
- RFC 6819 Refresh Token Reuse Detection: Automatic token theft detection with session lineage tracking (
familyId) and instant compromised family wipe. - TOTP Two-Factor Authentication (2FA): RFC 6238 compliant (Google Authenticator, Authy, 1Password) with AES-256-GCM encryption at rest, scoped Pre-Auth token gating, and 10 single-use hashed backup codes.
- Immutable Security Audit Trail: Append-only MongoDB collection logging all security lifecycle events (
LOGIN_SUCCESS,LOGIN_FAILED,TOKEN_REUSE_ATTACK_DETECTED,2FA_ENABLED, etc.) for SOC 2 compliance. - Dual-Token Architecture: Short-lived Access Tokens (15m) + Long-lived Refresh Tokens (7d) in
httpOnly,secure,sameSite: strictcookies. - Forgot & Reset Password Flow: High-entropy 32-byte cryptographic reset tokens with a 15-minute MongoDB TTL auto-expiry and automatic multi-device session revocation.
- Social Authentication (OAuth 2.0): 1-click Google & GitHub login via Passport with automatic email verification and account linking.
- Granular Brute-Force Rate Limiting: Dedicated rate limiters on
/login,/register,/verify-email,/forgot-password, and/2fa/verify. - Request Validation with Zod: Strict payload schemas, input sanitization, and password complexity enforcement.
- HTTP Security Headers with Helmet: Built-in defense against Clickjacking, MIME-sniffing, and XSS.
- CORS with Credentials: Whitelisted origin access allowing secure
httpOnlycookie transfer. - Native MongoDB TTL Auto-Deletion: 10-minute auto-deletion on OTPs and 15-minute auto-deletion on password resets.
- Multi-Device Revocation: Invalidate a single session or all active sessions across all devices (
/logout-all).
| Security & Feature Area | Baseline Version 1.0.0 | Hardened Version 2.0.0 | ๐ก๏ธ Version 3.0.0 (Current) | Why It Matters |
|---|---|---|---|---|
| Password Hashing | SHA-256 (crypto) |
Bcrypt (12 Salt Rounds) | Argon2id (19MB Memory-Hard) + Lazy Rehash | Immune to GPU/ASIC parallel cracking; zero-downtime migration. |
| Token Theft Mitigation | None | Basic Rotation | RFC 6819 Token Family Lineage Invalidation | Instantly revokes all active sessions if an old token is replayed. |
| Two-Factor Auth (2FA) | โ None | โ None | RFC 6238 TOTP + AES-256-GCM + 10 Hashed Backups | Phishing-resistant second factor; encrypted secret keys at rest. |
| 2FA Pre-Auth Gate | โ None | โ None | Scoped 5-Min Pre-Auth JWT (2FA_PENDING) |
Impossible to bypass 2FA by ignoring client-side UI redirects. |
| Audit Logging | โ None | โ None | Immutable MongoDB Audit Trail (SOC 2) | Non-blocking forensic trail capturing IP, userAgent, and events. |
| Social Logins | โ None | Google & GitHub OAuth 2.0 | Google & GitHub OAuth 2.0 with Account Linking | 1-click login with pre-verified email linking. |
| Account Recovery | Basic Reset | 32-Byte CSPRNG | 32-Byte CSPRNG + Multi-Device Session Wipe | 256-bit entropy token auto-invalidates all active sessions. |
| Brute-Force Defense | None | Rate Limiting | Granular Limiters on all 5 sensitive routes | Throttles credential stuffing, OTP guessing, and 2FA attacks. |
โโโ assets/
โ โโโ security_layers.png # Enterprise 6-pillar security architecture diagram
โ โโโ rfc6819_flow.png # RFC 6819 token family replay theft diagram
โ โโโ totp_2fa_flow.png # RFC 6238 TOTP 2FA & Pre-Auth flow diagram
โโโ src/
โ โโโ config/
โ โ โโโ config.js # Centralized environment variable validation
โ โ โโโ db.js # Mongoose database connection
โ โ โโโ passport.js # Google & GitHub OAuth 2.0 Passport strategies
โ โโโ controllers/
โ โ โโโ register.controller.js # Argon2id registration & OTP email verification
โ โ โโโ login.controller.js # Login, Argon2id lazy-rehash & Pre-Auth 2FA gate
โ โ โโโ twoFactor.controller.js # TOTP setup, activation, 2FA verify, disable & backup codes
โ โ โโโ token.controller.js # RFC 6819 token rotation, single logout & logout-all
โ โ โโโ password.controller.js # Forgot password & secure password reset flow
โ โ โโโ social.controller.js # Social OAuth callback & session issuance
โ โ โโโ auth.controller.js # Aggregator index re-exporting all controllers
โ โโโ emails/
โ โ โโโ otp.template.js # Dedicated responsive HTML OTP email template
โ โ โโโ resetPassword.template.js # Dedicated responsive HTML password reset email template
โ โโโ middleware/
โ โ โโโ auth.middleware.js # Bearer Access Token verification middleware
โ โ โโโ ratelimit.middleware.js # Rate limiters for login, register, OTP, reset & 2FA
โ โ โโโ validate.middleware.js # Generic Zod validation middleware
โ โโโ models/
โ โ โโโ user.model.js # User schema (Argon2id password, 2FA fields, OAuth IDs)
โ โ โโโ session.model.js # Session schema (familyId, isUsed, refreshTokenHash, ip)
โ โ โโโ auditLog.model.js # Append-only security audit collection (indexed)
โ โ โโโ otp.model.js # OTP schema with 10-min MongoDB TTL index
โ โ โโโ passwordReset.model.js # Password reset tokens with 15-min MongoDB TTL index
โ โโโ routes/
โ โ โโโ auth.route.js # Express auth routes with limiters & validators
โ โโโ services/
โ โ โโโ audit.service.js # Non-blocking async security event logger
โ โ โโโ email.service.js # Nodemailer service using Google OAuth2
โ โโโ utils/
โ โ โโโ crypto.utils.js # AES-256-GCM crypto, SHA-256, OTP & Backup Code generators
โ โ โโโ utils.js # Utilities re-exporter
โ โโโ validators/
โ โ โโโ auth.validator.js # Zod schemas for register, login, and verifyEmail
โ โ โโโ password.validator.js # Zod schemas for forgotPassword and resetPassword
โ โโโ app.js # Express app initialization, Helmet, CORS & middleware stack
โ โโโ server.js # Server entry point
โโโ SECURITY.md # Comprehensive 9-layer technical security documentation
โโโ .env.example # Environment variables template
โโโ package.json
โโโ LICENSE # ISC License
โโโ README.md
# Clone the repository
git clone https://github.com/adityajha-coder/AuthORS.git
# Enter the project directory
cd AuthORS
# Install dependencies
npm installCreate a .env file in the root directory:
cp .env.example .envFill in the .env variables (see the Process to Obtain & Update All API Keys & Credentials below).
# Development mode (with nodemon)
npm run dev
# Production mode
node src/server.jsIf you are connecting this backend to a frontend (React, Next.js, Vue, or HTML), here is the exact list of files where frontend URLs are handled:
Set FRONTEND_URL to your frontend's host:
# For local Vite frontend:
FRONTEND_URL=http://localhost:5173
# For local React / Next.js frontend:
# FRONTEND_URL=http://localhost:3000
# For deployed live website:
# FRONTEND_URL=https://your-production-app.com-
src/app.js(CORS): Readsprocess.env.FRONTEND_URLto allow cross-origin cookies from your frontend. -
src/controllers/password.controller.js(Password Reset Email): Usesprocess.env.FRONTEND_URLto construct the clickable reset link sent in emails:const resetUrl = `${process.env.FRONTEND_URL || "http://localhost:5173"}/reset-password?token=${rawToken}&email=${encodeURIComponent(email)}`;
-
src/controllers/social.controller.js(Google & GitHub Redirect): Usesprocess.env.FRONTEND_URLto redirect the user back to your frontend dashboard with their access token:const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173"; return res.redirect(`${frontendUrl}/auth/callback?token=${accessToken}`);
You can use a local MongoDB instance or a free cloud cluster on MongoDB Atlas:
- Go to MongoDB Atlas and log in or create a free account.
- Click Build a Database and select the M0 Free Shared Tier.
- Under Security
$\rightarrow$ Database Access:- Click Add New Database User.
- Set Authentication Method to Password. Choose a username and a strong password.
- Set Database User Privileges to Read and write to any database.
- Under Security
$\rightarrow$ Network Access:- Click Add IP Address.
- Click Allow Access from Anywhere (
0.0.0.0/0) or add your current IP address.
- Under Database
$\rightarrow$ Clusters, click Connect:- Select Drivers (Node.js).
- Copy the connection string:
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/auth_system?retryWrites=true&w=majority
- Replace
<username>and<password>with your database user credentials.
๐ก How to update: If you change your database password or switch to a new cluster, update the MONGO_URI string in .env and restart the server.
To securely sign JWT access and refresh tokens, generate a cryptographically strong 256-bit secret string.
Run this command in your terminal:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Copy the generated 64-character hexadecimal string and assign it to JWT_SECRET in your .env:
JWT_SECRET=f4a7c89b2134567890abcdef1234567890abcdef1234567890abcdef12345678๐ก How to update: Changing JWT_SECRET in .env will immediately invalidate all existing active JWT tokens across all users (forcing everyone to log in again).
Google OAuth 2.0 handles both Nodemailer automated emails and Google 1-Click Login.
- Open the Google Cloud Console.
- Click the project dropdown at the top
$\rightarrow$ New Project$\rightarrow$ Name itAuth-Email-Service$\rightarrow$ Click Create.
- In the search bar at the top, search for Gmail API.
- Click Gmail API and click Enable.
- In the left sidebar, navigate to APIs & Services
$\rightarrow$ OAuth consent screen. - Select External and click Create.
- Fill in:
-
App name:
Auth System - User support email: Select your Gmail.
- Developer contact email: Enter your email address.
-
App name:
- Click Save and Continue through Scopes.
- Under Test Users:
- Click Add Users.
- Enter your Gmail address (the address you will send emails from).
- Click Save and Continue.
- In the left sidebar, click Credentials.
- Click + Create Credentials
$\rightarrow$ OAuth client ID. - Choose:
-
Application type:
Web application -
Name:
AuthORS Web Client -
Authorized redirect URIs: Click + Add URI and add BOTH:
https://developers.google.com/oauthplayground http://localhost:3001/api/auth/google/callback
-
Application type:
- Click Create.
- Copy your Client ID and Client Secret:
GOOGLE_CLIENT_ID=...GOOGLE_CLIENT_SECRET=...
- Open the Google OAuth 2.0 Playground.
- In the top-right corner, click the Gear Icon (OAuth 2.0 configuration):
- Check the box "Use your own OAuth credentials".
- Paste your OAuth Client ID and OAuth Client Secret.
- On the left under Step 1 Select & authorize APIs:
- Scroll down to Gmail API v1.
- Select
https://mail.google.com/. - Click Authorize APIs.
- Log in with the same Gmail account you added as a test user and click Continue (allow permissions).
- In Step 2 Exchange authorization code for tokens:
- Click Exchange authorization code for tokens.
- Copy the resulting Refresh token:
GOOGLE_REFRESH_TOKEN=1//04...
๐ก How to update: If you deploy your backend to production (e.g. https://api.yourdomain.com), go to Google Cloud Console $\rightarrow$ Credentials $\rightarrow$ Edit your OAuth Client ID $\rightarrow$ Add https://api.yourdomain.com/api/auth/google/callback to Authorized redirect URIs $\rightarrow$ Click Save.
- Open GitHub Developer Settings.
- Click New OAuth App (or Register a new application).
- Fill in:
- Application name:
AuthORS - Homepage URL:
http://localhost:3001 - Authorization callback URL:
http://localhost:3001/api/auth/github/callback
- Application name:
- Click Register application.
- Click Generate a new client secret.
- Copy
Client IDandClient Secretinto your.env:GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret
๐ก How to update: When deploying to production, go to your GitHub OAuth App settings and update the Authorization callback URL to https://api.yourdomain.com/api/auth/github/callback.
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/auth_system?retryWrites=true&w=majority
JWT_SECRET=your_jwt_secret_key
GOOGLE_USER=your_email@gmail.com
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_token
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
FRONTEND_URL=http://localhost:5173All endpoints are prefixed with /api/auth.
| Method | Endpoint | Rate Limit | Request Body Validation / Requirements | Auth Required |
|---|---|---|---|---|
POST |
/api/auth/register |
5 / hr | userName (3-15 chars, alphanumeric), email, password (min 8 chars, 1 uppercase, 1 number, 1 special char) |
โ No |
POST |
/api/auth/verify-email |
3 / 10m | email, otp (exactly 6 digits) |
โ No |
POST |
/api/auth/login |
5 / 15m | email, password |
โ No |
GET |
/api/auth/get-me |
None | None | ๐ Yes (Bearer <accessToken>) |
GET |
/api/auth/refresh-token |
None | None | ๐ Yes (via httpOnly Cookie) |
POST |
/api/auth/logout |
None | None | ๐ Yes (via httpOnly Cookie) |
POST |
/api/auth/logout-all |
None | None | ๐ Yes (via httpOnly Cookie) |
POST |
/api/auth/forgot-password |
3 / hr | email (valid email string) |
โ No |
POST |
/api/auth/reset-password |
None | email, token (min 32 chars), password (min 8 chars, 1 uppercase, 1 number, 1 special char) |
โ No |
POST |
/api/auth/2fa/setup |
None | None | ๐ Yes (Bearer <accessToken>) |
POST |
/api/auth/2fa/enable |
None | code (6-digit TOTP code) |
๐ Yes (Bearer <accessToken>) |
POST |
/api/auth/2fa/verify |
5 / 5m | preAuthToken, code (TOTP) OR backupCode |
โ No (Pre-Auth Token) |
POST |
/api/auth/2fa/disable |
None | password OR code (re-authentication) |
๐ Yes (Bearer <accessToken>) |
POST |
/api/auth/2fa/regenerate-backup-codes |
None | password OR code |
๐ Yes (Bearer <accessToken>) |
GET |
/api/auth/google |
None | None (Redirects to Google consent screen) | โ No |
GET |
/api/auth/google/callback |
None | OAuth code (Browser redirect) | โ No |
GET |
/api/auth/github |
None | None (Redirects to GitHub consent screen) | โ No |
GET |
/api/auth/github/callback |
None | OAuth code (Browser redirect) | โ No |
POST /api/auth/register
// Request Body
{
"userName": "adityajha",
"email": "aditya@example.com",
"password": "StrongPassword123!"
}
// Success Response (201 Created)
{
"message": "User registered successfully",
"user": {
"userName": "adityajha",
"email": "aditya@example.com",
"verified": false
}
}// Validation Error Response (400 Bad Request) - e.g. Weak Password
{
"message": "Password must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character (@$!%*?&)",
"errors": [
{
"field": "password",
"message": "Password must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character (@$!%*?&)"
}
]
}// Duplicate Error Response (409 Conflict)
{
"message": "Email is already registered"
}// Rate Limit Error (429 Too Many Requests)
{
"message": "Too many accounts created, please try again after an hour."
}POST /api/auth/verify-email
// Request Body
{
"email": "aditya@example.com",
"otp": "492817"
}
// Success Response (200 OK)
{
"message": "Email verified successfully",
"user": {
"userName": "adityajha",
"email": "aditya@example.com",
"verified": true
}
}// Invalid OTP / Expired Response (400 Bad Request)
{
"message": "Invalid OTP"
}// Rate Limit Error (429 Too Many Requests)
{
"message": "Too many otp verification attempts, please request new otp or try again in 10 minutes."
}POST /api/auth/login
// Request Body
{
"email": "aditya@example.com",
"password": "StrongPassword123!"
}
// Success Response (Standard Login - 200 OK)
// Set-Cookie: refreshToken=<7d_JWT_Token>; HttpOnly; Secure; SameSite=Strict; Max-Age=604800
{
"message": "Logged in successfully",
"user": {
"userName": "adityajha",
"email": "aditya@example.com"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}// Success Response (When 2FA is Enabled on Account - 200 OK)
// Issues a short-lived 5-minute Pre-Auth Token (stage: "2FA_PENDING")
{
"require2FA": true,
"preAuthToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"message": "Two-factor authentication required. Verify with your 6-digit code or backup code."
}// Unverified Account Response (401 Unauthorized)
{
"message": "Email not verified"
}// Invalid Credentials Response (401 Unauthorized)
{
"message": "Invalid email or password"
}// Rate Limit Error (429 Too Many Requests)
{
"message": "Too many login attempts. Please try again after 15 minutes."
}POST /api/auth/forgot-password
// Request Body
{
"email": "aditya@example.com"
}
// Success Response (200 OK)
{
"message": "If an account with that email exists, a password reset link has been sent."
}// Rate Limit Error (429 Too Many Requests)
{
"message": "Too many password reset requests. Please try again after an hour."
}POST /api/auth/reset-password
// Request Body
{
"email": "aditya@example.com",
"token": "3a2d635651159aef4f5fafd55686b6b9636d53b044e58913932148acf078e97a",
"password": "NewStrongPassword123!"
}
// Success Response (200 OK)
{
"message": "Password reset successful. All active sessions have been revoked. Please log in with your new password."
}// Invalid / Expired Token Response (400 Bad Request)
{
"message": "Invalid or expired password reset link"
}In your frontend HTML / React application, create direct links to the trigger routes:
<!-- Google Login Button -->
<a href="http://localhost:3001/api/auth/google">
<button>Sign in with Google</button>
</a>
<!-- GitHub Login Button -->
<a href="http://localhost:3001/api/auth/github">
<button>Sign in with GitHub</button>
</a>Callback Handling: After authentication, the backend sets the refreshToken cookie and redirects the browser to:
${FRONTEND_URL}/auth/callback?token=<accessToken>
GET /api/auth/get-me
Headers:
Authorization: Bearer <accessToken>
// Success Response (200 OK)
{
"message": "User fetched successfully",
"user": {
"userName": "adityajha",
"email": "aditya@example.com"
}
}// Unauthorized Response (401 Unauthorized) - e.g. Expired Token
{
"message": "Invalid or expired token"
}GET /api/auth/refresh-token
Cookie: refreshToken=<refreshToken>
// Success Response (200 OK)
// Set-Cookie: refreshToken=<newRotatedRefreshToken>; HttpOnly; Secure; SameSite=Strict; Max-Age=604800
{
"message": "Access token refreshed successfully",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}// Invalid Session / Token Error (401 Unauthorized)
{
"message": "Invalid or expired refresh token"
}POST /api/auth/logout
Cookie: refreshToken=<refreshToken>
// Success Response (200 OK)
// Set-Cookie: refreshToken=; Max-Age=0
{
"message": "Logged out successfully"
}POST /api/auth/logout-all
Cookie: refreshToken=<refreshToken>
// Success Response (200 OK)
// Set-Cookie: refreshToken=; Max-Age=0
{
"message": "Logged out from all devices successfully"
}POST /api/auth/2fa/setup
Headers:
Authorization: Bearer <accessToken>
// Success Response (200 OK)
{
"message": "Scan this QR code with Google Authenticator",
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAI...",
"manualEntryKey": "47G74FGY2NVRB44U4QZ4L2R3MY4Q7A3X"
}POST /api/auth/2fa/enable
Headers:
Authorization: Bearer <accessToken>// Request Body
{
"code": "492108"
}
// Success Response (200 OK)
{
"message": "2FA successfully enabled! Store these 10 backup codes in a safe place. They will only be shown once.",
"backupCodes": [
"A1B2C3D4",
"E5F6G7H8",
"I9J0K1L2",
"M3N4O5P6",
"Q7R8S9T0",
"U1V2W3X4",
"Y5Z6A7B8",
"C9D0E1F2",
"G3H4I5J6",
"K7L8M9N0"
]
}POST /api/auth/2fa/verify
// Request Body (Option A: 6-Digit Authenticator Code)
{
"preAuthToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"code": "831774"
}// Request Body (Option B: Single-Use Emergency Backup Code)
{
"preAuthToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"backupCode": "A1B2C3D4"
}// Success Response (200 OK)
// Set-Cookie: refreshToken=<7d_JWT_Token>; HttpOnly; Secure; SameSite=Strict; Max-Age=604800
{
"message": "2FA verified! Login completed successfully.",
"user": {
"userName": "adityajha",
"email": "aditya@example.com"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}POST /api/auth/2fa/disable
Headers:
Authorization: Bearer <accessToken>// Request Body (Re-authenticate with current Password OR current TOTP Code)
{
"password": "StrongPassword123!",
"code": "492108"
}
// Success Response (200 OK)
{
"message": "2FA has been successfully disabled."
}POST /api/auth/2fa/regenerate-backup-codes
Headers:
Authorization: Bearer <accessToken>// Request Body (Re-authenticate with Password OR current TOTP Code)
{
"password": "StrongPassword123!"
}
// Success Response (200 OK)
{
"message": "Old backup codes invalidated. Here are your 10 new single-use backup codes:",
"backupCodes": [
"Z1Y2X3W4",
"V5U6T7S8",
"R9Q0P1O2",
"N3M4L5K6",
"J7I8H9G0",
"F1E2D3C4",
"B5A6Z7Y8",
"X9W0V1U2",
"T3S4R5Q6",
"P7O8N9M0"
]
}-
Copy the
src/folder into your new project. -
Install the required packages:
npm install express mongoose dotenv jsonwebtoken cookie-parser morgan nodemailer argon2 zod helmet cors express-rate-limit passport passport-google-oauth20 passport-github2 otplib qrcode
-
Mount the auth router in your main
app.js:import express from "express"; import cookieParser from "cookie-parser"; import helmet from "helmet"; import cors from "cors"; import authRouter from "./routes/auth.route.js"; const app = express(); // Security headers & CORS app.use(helmet()); app.use( cors({ origin: [ "http://localhost:3000", "http://localhost:5173", process.env.FRONTEND_URL, ].filter(Boolean), credentials: true, }), ); app.use(express.json()); app.use(cookieParser()); // Routes app.use("/api/auth", authRouter);
-
Copy
.env.exampleto.envand set your credentials.
ISC License. Free to use and adapt for personal and commercial projects.
