Aller au contenu principal

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 chars
  • first_name: Required, min 2 chars
  • username: Required, min 3 chars, alphanumeric + underscore
  • email: Required, valid email format
  • password: Required, min 8 chars, must include uppercase, lowercase, number, special char
  • password_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:

  1. Validate credentials
  2. Generate access token (1h) + refresh token (7d)
  3. Cache user + auth data in Redis (1h TTL)
  4. Create login history entry with device/location info
  5. 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:

  1. Invalidate cache (user:auth:{userId})
  2. Delete login history entry
  3. 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:

  1. Validate reset token
  2. Update password (bcrypt hashed)
  3. Invalidate all caches for user
  4. Delete all login history (force re-login)
  5. 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:

  1. Force password reset (generate reset token)
  2. Delete all login history
  3. Invalidate all caches
  4. 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​

MiddlewarePurpose
Middleware.xApiKeyValidate API key in x-api-key header
Middleware.isLoginAuthenticate user via JWT token
Middleware.rateLimitRate 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​

OperationWithout CacheWith CacheImprovement
Auth check50-100ms< 1ms50-100x faster
API requestDB query each timeCache hitMassive 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_SECRET matches 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​


Questions? Check the GitHub Discussions or open an issue.