Authentication System
Express Base API includes a complete, production-ready authentication system with JWT tokens, refresh tokens, OTP verification, password reset, and automatic session management.
Featuresβ
- π JWT Authentication - Secure token-based authentication
- π Refresh Tokens - Long-lived tokens for seamless experience
- βοΈ Email Verification - OTP-based email confirmation
- π Password Reset - Secure password recovery flow
- π± Device Tracking - Track user sessions across devices
- π Location Tracking - IP-based geolocation
- β‘ Redis Caching - Ultra-fast authentication checks (< 1ms)
- π Login History - Automatic 90-day TTL cleanup
- π‘οΈ Security Alerts - Notify users of suspicious activity
Authentication Flowβ
βββββββββββββββ
β Register β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ ββββββββββββββββ
β Send OTP ββββββΆβ Email Sent β
ββββββββ¬βββββββ ββββββββββββββββ
β
βΌ
βββββββββββββββ
β Verify OTP β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ ββββββββββββββββ
β Login ββββββΆβ Access Token β
β β β (1 hour) β
β β ββββββββββββββββ€
β β β Refresh Tokenβ
β β β (7 days) β
ββββββββ¬βββββββ ββββββββββββββββ
β
βΌ
βββββββββββββββ
β Cached in β
β Redis (1h) β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β Make API β
β Requests β
βββββββββββββββ
API Endpointsβ
Registerβ
Create a new user account.
POST /auth/register
Content-Type: application/json
{
"last_name": "Doe",
"first_name": "John",
"username": "johndoe",
"email": "john@example.com",
"password": "SecureP@ss123",
"password_confirmation": "SecureP@ss123"
}
Response (201):
{
"status_code": 201,
"status": "SUCCESS",
"message": "Registration successful. Please check your email for OTP.",
"data": {
"user": {
"_id": "65abc123...",
"username": "johndoe",
"email": "john@example.com",
"last_name": "Doe",
"first_name": "John"
},
"otp_sent": true
}
}
Validation Rules:
last_name: Required, min 2 charsfirst_name: Required, min 2 charsusername: Required, min 3 chars, alphanumeric + underscoreemail: Required, valid email formatpassword: Required, min 8 chars, must include uppercase, lowercase, number, special charpassword_confirmation: Must match password
Verify Email (OTP)β
Activate account with OTP received via email.
POST /auth/verify-otp
Content-Type: application/json
{
"email": "john@example.com",
"otp": "123456"
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Email verified successfully",
"data": {
"verified": true
}
}
OTP Details:
- 6-digit numeric code
- Valid for 10 minutes
- Sent to user's email
- One-time use only
Loginβ
Authenticate and receive access + refresh tokens.
POST /auth/login
Content-Type: application/json
{
"login": "johndoe", // username or email
"password": "SecureP@ss123"
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Login successful",
"data": {
"user": {
"_id": "65abc123...",
"username": "johndoe",
"email": "john@example.com",
"last_name": "Doe",
"first_name": "John"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}
}
What Happens on Login:
- Validate credentials
- Generate access token (1h) + refresh token (7d)
- Cache user + auth data in Redis (1h TTL)
- Create login history entry with device/location info
- Return tokens
Refresh Tokenβ
Get new access token using refresh token.
POST /auth/refresh-token
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Token refreshed successfully",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}
}
Logoutβ
End session and invalidate tokens.
POST /auth/logout
Authorization: Bearer {access_token}
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Logout successful"
}
What Happens on Logout:
- Invalidate cache (
user:auth:{userId}) - Delete login history entry
- Optionally delete all sessions
Forgot Passwordβ
Request password reset token.
POST /auth/forgot-password
Content-Type: application/json
{
"email": "john@example.com"
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Password reset instructions sent to your email"
}
Email Sent:
- Reset token (valid 15 minutes)
- Reset link:
{FRONTEND_URL}/reset-password?token={token}
Reset Passwordβ
Set new password using reset token.
POST /auth/reset-password
Content-Type: application/json
{
"token": "abc123xyz...",
"password": "NewSecureP@ss123",
"password_confirmation": "NewSecureP@ss123"
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Password reset successfully. You can now login with your new password."
}
What Happens on Reset:
- Validate reset token
- Update password (bcrypt hashed)
- Invalidate all caches for user
- Delete all login history (force re-login)
- Invalidate reset token
Resend OTPβ
Request new OTP for email verification.
POST /auth/resend-otp
Content-Type: application/json
{
"email": "john@example.com"
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "New OTP sent to your email"
}
"It's Not Me" Security Alertβ
Report suspicious login activity.
POST /auth/its-not-me
Authorization: Bearer {access_token}
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200):
{
"status_code": 200,
"status": "SUCCESS",
"message": "Security measures applied. All sessions terminated."
}
What Happens:
- Force password reset (generate reset token)
- Delete all login history
- Invalidate all caches
- Send security alert email with reset link
Authentication Middlewareβ
Protect Routesβ
import { Middleware } from './configs/app.config';
const routes = [
{
path: '/product',
middlewares: [
Middleware.xApiKey, // Require API key
Middleware.isLogin, // Require authentication
],
},
];
Role-Based Accessβ
// In your controller or service
if (req.auth?.role !== 'admin') {
return response.error(res, 'Unauthorized', 403);
}
Available Middlewareβ
| Middleware | Purpose |
|---|---|
Middleware.xApiKey | Validate API key in x-api-key header |
Middleware.isLogin | Authenticate user via JWT token |
Middleware.rateLimit | Rate limiting (100 requests/15min) |
Caching for Performanceβ
The authentication system leverages Redis caching for blazing-fast performance.
Cache Key Formatβ
user:auth:{userId}
Cache Data Structureβ
{
"user": {
"_id": "65abc123...",
"last_name": "Doe",
"first_name": "John",
"username": "johndoe",
"email": "john@example.com"
},
"auth": {
"_id": "65abc456...",
"role": null,
"confirmed_at": "2024-01-15T10:30:00.000Z"
}
}
Performance Impactβ
| Operation | Without Cache | With Cache | Improvement |
|---|---|---|---|
| Auth check | 50-100ms | < 1ms | 50-100x faster |
| API request | DB query each time | Cache hit | Massive DB load reduction |
Cache Flowβ
// In auth middleware (utils/middlewares/auth/auth.middleware.ts)
async function getUser(token: string) {
// 1. Verify JWT token
const verified = jwt.verify(token, JWT_SECRET);
// 2. Check cache first
const cacheKey = `user:auth:${verified.id}`;
const cachedData = await cacheService.get(cacheKey);
if (cachedData) {
// Cache hit (< 1ms)
return { error: false, data: cachedData };
}
// 3. Cache miss - query database
const user = await User.findById(verified.id);
const auth = await Auth.findOne({ user: verified.id });
// 4. Cache for 1 hour
await cacheService.set(cacheKey, { user, auth }, 3600);
return { error: false, data: { user, auth } };
}
Login Historyβ
Every login creates an entry in the login_histories collection with automatic cleanup after 90 days.
Data Trackedβ
interface ILoginHistory {
auth: ObjectId; // Reference to auth document
user: ObjectId; // Reference to user document
ip: string; // IP address
token: string; // Access token
refresh_token: string; // Refresh token
login_at: Date; // Login timestamp
devices?: { // Device information
browser?: {
name: string; // e.g., "Chrome"
version: string; // e.g., "120.0.0.0"
major: string; // e.g., "120"
};
os?: {
name: string; // e.g., "Linux"
version: string; // e.g., "x86_64"
};
platform?: string; // e.g., "Linux x86_64"
source?: string; // e.g., "Desktop"
};
locations?: { // Location information
country?: string;
region?: string;
city?: string;
timezone?: string;
};
user_agent?: string; // Full user agent string
}
TTL (Time-To-Live) Cleanupβ
MongoDB automatically deletes entries older than 90 days:
// TTL index on login_at field
LoginHistory.schema.index(
{ login_at: 1 },
{ expireAfterSeconds: 7776000 } // 90 days
);
Benefits:
- No manual cleanup required
- Consistent data size
- Fast queries (indexed)
- Audit trail for 90 days
Query Login Historyβ
import loginHistoryService from './src/services/auth/login-history.service';
// Get user's login history (last 50)
const history = await loginHistoryService.getByUser(userId, {
limit: 50,
sort: { login_at: -1 }
});
// Get login statistics
const stats = await loginHistoryService.getLoginStats(userId);
console.log(stats);
// {
// total_logins: 156,
// unique_ips: 8,
// unique_devices: 3,
// last_login: "2024-01-15T10:30:00.000Z"
// }
Security Best Practicesβ
Password Requirementsβ
Enforced via Joi validation:
password: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/)
.required()
.messages({
'string.pattern.base': 'Password must include uppercase, lowercase, number, and special character'
})
JWT Configurationβ
# .env
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_REFRESH_SECRET=your-refresh-secret-key
JWT_EXPIRES=1h # Access token lifetime
JWT_REFRESH_EXPIRES=7d # Refresh token lifetime
OTP_EXPIRES=10m # OTP validity
Rate Limitingβ
Authentication endpoints are rate-limited:
// 5 attempts per 15 minutes per IP
Middleware.rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts. Please try again later.'
})
Password Hashingβ
Bcrypt with 10 rounds:
import bcrypt from 'bcrypt';
const hashedPassword = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare(password, hashedPassword);
Configurationβ
Environment Variablesβ
# JWT Configuration
JWT_SECRET=your-jwt-secret-change-in-production
JWT_REFRESH_SECRET=your-refresh-secret
JWT_EXPIRES=1h
JWT_REFRESH_EXPIRES=7d
OTP_EXPIRES=10m
# Frontend URL (for email links)
FRONTEND_URL=http://localhost:5173
# Email Configuration (for OTP and password reset)
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=your-smtp-user
SMTP_PASS=your-smtp-password
SMTP_FROM=noreply@example.com
Customize Token Expiryβ
Edit utils/helpers/jwt-expiry.helper.ts:
export const getJWTExpiry = (expiresIn: string = '1h'): number => {
// Custom logic for token expiry
};
Testing Authenticationβ
With cURLβ
# 1. Register
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{
"last_name": "Doe",
"first_name": "John",
"username": "johndoe",
"email": "john@example.com",
"password": "SecureP@ss123",
"password_confirmation": "SecureP@ss123"
}'
# 2. Verify OTP (check email)
curl -X POST http://localhost:3000/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"otp": "123456"
}'
# 3. Login
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{
"login": "johndoe",
"password": "SecureP@ss123"
}'
# 4. Use token in requests
curl -X GET http://localhost:3000/api/profile \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "x-api-key: YOUR_API_KEY"
Troubleshootingβ
"Invalid or expired token"β
Cause: Token expired or invalid signature
Solution:
- Use refresh token to get new access token
- If refresh token expired, re-login
- Check
JWT_SECRETmatches across environments
"Email not verified"β
Cause: User hasn't confirmed email with OTP
Solution:
- Resend OTP via
/auth/resend-otp - Check email spam folder
- Verify SMTP configuration
"Too many login attempts"β
Cause: Rate limit exceeded
Solution:
- Wait 15 minutes
- Check IP address (shared IPs may hit limits faster)
- Increase rate limit in configuration if needed
Next Stepsβ
- Login History - Detailed login tracking
- Caching System - Performance optimization
- Email System - Template-based emails
- Rate Limiting - API protection
Questions? Check the GitHub Discussions or open an issue.