A production-grade, secure REST API built with Express.js 5 and MongoDB (Mongoose 9) that demonstrates 16 security layers applied to a user authentication system. Designed as a teaching resource and reference architecture for building hardened Node.js APIs.
- Features
- Security Layers
- Project Structure
- Prerequisites
- Installation
- Environment Variables
- Running the Server
- API Endpoints
- Testing with cURL
- Tech Stack
- License
- User Registration with strong password policy enforcement
- User Login with account lockout after failed attempts
- JWT Authentication with token blacklisting for secure logout
- Password Change with re-authentication and old token revocation
- Account Deletion with password re-confirmation
- Profile Retrieval with sensitive data masking (prevents IDOR)
- 16 layered security controls applied across the entire stack
| # | Layer | Technology / Technique |
|---|---|---|
| 1 | HTTP Security Headers | helmet β sets ~14 browser security headers (CSP, HSTS, X-Frame-Options, etc.) |
| 2 | CORS (Cross-Origin Resource Sharing) | Origin whitelist β only trusted front-ends can call the API |
| 3 | Global Rate Limiting | express-rate-limit β 100 req / 15 min per IP |
| 4 | Auth Route Rate Limiting | Stricter limit β 10 req / 15 min on login, signup, etc. |
| 5 | Body Size Limit | express.json({ limit: '10kb' }) β prevents memory exhaustion |
| 6 | NoSQL Injection Prevention | Custom sanitizer β strips $ and . operators from input |
| 7 | XSS Sanitization | Custom HTML entity encoder β escapes <, >, ", ', & |
| 8 | HTTP Parameter Pollution | hpp β deduplicates query parameters |
| 9 | Input Validation | express-validator chains β validates and sanitises every field |
| 10 | Password Hashing | bcryptjs with cost factor 12 (4096 iterations) |
| 11 | JWT Authentication | Bearer token verification + signature validation |
| 12 | Token Blacklisting | Revoked tokens stored in MongoDB with TTL auto-cleanup |
| 13 | Account Lockout | 5 failed attempts β 15-minute lock (brute-force defence) |
| 14 | Strong Password Policy | Min 8 chars, uppercase, lowercase, digit, special character |
| 15 | Sensitive Data Masking | safeUser() filter β password/lockout fields never returned |
| 16 | Global Error Handler | No stack traces in production β generic error messages only |
Security/
βββ src/
β βββ config/
β β βββ db.js # MongoDB connection with Mongoose
β β βββ env.js # Fail-fast environment variable validation
β βββ controllers/
β β βββ auth.controller.js # Signup, login, logout, change password, delete account, profile
β βββ middleware/
β β βββ auth.middleware.js # JWT verification + blacklist check (protect)
β β βββ validate.js # Centralised express-validator error handler
β βββ models/
β β βββ User.js # User schema with bcrypt hashing + account lockout
β β βββ TokenBlacklist.js # JWT blacklist with MongoDB TTL index
β βββ routes/
β β βββ auth.routes.js # Auth routes with per-route security layers
β βββ validators/
β β βββ auth.validator.js # Input validation chains for all auth endpoints
β βββ app.js # Express app β middleware stack + routes
β βββ server.js # Entry point β connects DB and starts server
βββ .env.example # Template for environment variables
βββ .gitignore
βββ package.json
βββ README.md
- Node.js v18 or later
- MongoDB (local instance or MongoDB Atlas free tier)
- npm (bundled with Node.js)
# 1. Clone the repository
git clone https://github.com/drfawzyofficial/security.git
cd security
# 2. Install dependencies
npm install
# 3. Create your environment file
cp .env.example .env
# Then edit .env with your actual values (see below)Create a .env file in the project root using .env.example as a template:
PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/security_demo
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRES_IN=7d
ALLOWED_ORIGINS=http://localhost:3000| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 5000 |
Server port |
NODE_ENV |
No | development |
development shows full errors; production hides stack traces |
MONGODB_URI |
Yes | β | MongoDB connection string |
JWT_SECRET |
Yes | β | Secret key used to sign JWT tokens |
JWT_EXPIRES_IN |
No | 7d |
Token expiry duration (e.g. 1h, 7d, 30d) |
ALLOWED_ORIGINS |
No | http://localhost:3000 |
Comma-separated list of allowed CORS origins |
β οΈ Never commit your.envfile β it contains secrets. The.gitignorealready excludes it.
npm run devnpm startThe server will output:
βββββββββββββββββββββββββββββββββββββ
π Security Demo API β Running
Mode : development
Port : 5000
URL : http://localhost:5000
βββββββββββββββββββββββββββββββββββββ
All endpoints are prefixed with /api/auth.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/signup |
β | Register a new user |
POST |
/api/auth/login |
β | Log in and receive JWT |
POST |
/api/auth/logout |
β JWT | Revoke current token |
PATCH |
/api/auth/change-password |
β JWT | Change password (re-auth required) |
GET |
/api/auth/me |
β JWT | Get current user profile |
DELETE |
/api/auth/account |
β JWT | Permanently delete account |
// POST /api/auth/signup
// Request Body:
{
"name": "Ahmed Ali",
"email": "ahmed@example.com",
"password": "MyP@ssw0rd!"
}
// Response (201):
{
"success": true,
"message": "Account created successfully",
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "...",
"name": "Ahmed Ali",
"email": "ahmed@example.com",
"role": "user",
"createdAt": "2026-04-16T..."
}
}// POST /api/auth/login
// Request Body:
{
"email": "ahmed@example.com",
"password": "MyP@ssw0rd!"
}
// Response (200):
{
"success": true,
"message": "Logged in successfully",
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { ... }
}// PATCH /api/auth/change-password
// Headers: Authorization: Bearer <token>
// Request Body:
{
"currentPassword": "MyP@ssw0rd!",
"newPassword": "NewS3cur3P@ss!"
}
// Response (200):
{
"success": true,
"message": "Password changed successfully β please use the new token",
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { ... }
}# Signup
curl -X POST http://localhost:5000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com","password":"Test@1234"}'
# Login
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Test@1234"}'
# Get Profile (replace <TOKEN> with actual JWT)
curl http://localhost:5000/api/auth/me \
-H "Authorization: Bearer <TOKEN>"
# Change Password
curl -X PATCH http://localhost:5000/api/auth/change-password \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{"currentPassword":"Test@1234","newPassword":"NewTest@5678"}'
# Logout
curl -X POST http://localhost:5000/api/auth/logout \
-H "Authorization: Bearer <TOKEN>"
# Delete Account
curl -X DELETE http://localhost:5000/api/auth/account \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-d '{"password":"NewTest@5678"}'| Package | Version | Purpose |
|---|---|---|
express |
5.x | Web framework |
mongoose |
9.x | MongoDB ODM |
bcryptjs |
3.x | Password hashing |
jsonwebtoken |
9.x | JWT generation & verification |
helmet |
8.x | HTTP security headers |
cors |
2.x | Cross-origin resource sharing |
express-rate-limit |
8.x | Rate limiting |
express-validator |
7.x | Input validation & sanitisation |
hpp |
0.2.x | HTTP parameter pollution protection |
dotenv |
17.x | Environment variable loading |
nodemon |
3.x | Development auto-reload |
ISC
Built with β€οΈ for teaching secure API development.