Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

2 Commits

Folders and files

Repository files navigation

πŸ›‘οΈ AuthForge - Production-Ready Authentication System

Clone. Configure. Deploy. A complete authentication system you can use in your production applications right now.

Node.js PostgreSQL Redis Docker React

⚑ Use This For Your Production Authentication

Stop building auth from scratch. This template includes everything you need:

  • βœ… Secure by default - bcrypt, JWT, httpOnly cookies, rate limiting
  • βœ… One-command setup - Just run docker-compose up
  • βœ… Beautiful UI included - Login, Register, Dashboard ready to use
  • βœ… Easy to customize - Clean code, well-documented, modular design
  • βœ… Battle-tested patterns - Token rotation, MFA, audit logs

πŸ“š Table of Contents

  1. Quick Start
  2. What You'll Learn
  3. System Architecture
  4. Security Concepts Explained
  5. API Reference
  6. Project Structure
  7. Configuration
  8. Deployment

πŸš€ Quick Start

Prerequisites

3 Steps to Run

# 1. Clone the repository
git clone https://github.com/YOUR_USERNAME/authforge.git
cd authforge

# 2. Start everything with Docker
docker-compose up -d --build

# 3. Open the app
# Frontend: http://localhost:5173
# API: http://localhost:3000
# Emails: http://localhost:8025

That's it! πŸŽ‰ No Node.js installation needed. Everything runs in Docker.


πŸ›‘οΈ What This System Includes

This is a complete, production-ready authentication system with:

Feature What It Does
Password Security bcrypt hashing + unique salt per user prevents rainbow table attacks
JWT Access Tokens Stateless 15-min tokens that don't require database lookups
Refresh Token Rotation 7-day tokens with automatic theft detection
OAuth 2.0 "Login with Google" using industry-standard OAuth flow
Email MFA Optional 2-factor authentication via email OTP
Rate Limiting Blocks brute force attacks (5 attempts/15 min)
Account Lockout Locks account after 5 failed login attempts
Session Management View and revoke active sessions from any device
Audit Logging Every security event recorded in the database
Role-Based Access USER, MODERATOR, ADMIN, SUPER_ADMIN roles

πŸ—οΈ System Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         FRONTEND (React)                        β”‚
β”‚                    http://localhost:5173                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Login   β”‚  β”‚ Register β”‚  β”‚  Verify  β”‚  β”‚    Dashboard     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚             β”‚             β”‚                 β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                    HTTP (JSON) + Cookies
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      BACKEND (Express.js)                       β”‚
β”‚                    http://localhost:3000                        β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Routes    β”‚  β”‚  Middleware β”‚  β”‚       Controllers       β”‚  β”‚
β”‚  β”‚  /auth/*    │──│ Rate Limit  │──│  Login, Register, etc   β”‚  β”‚
β”‚  β”‚  /oauth/*   β”‚  β”‚ JWT Verify  β”‚  β”‚                         β”‚  β”‚
β”‚  β”‚  /user/*    β”‚  β”‚ RBAC        β”‚  β”‚                         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                β”‚                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                       SERVICES                            β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚  β”‚
β”‚  β”‚  β”‚  Hash   β”‚ β”‚  Token  β”‚ β”‚   OTP   β”‚ β”‚  Email  β”‚          β”‚  β”‚
β”‚  β”‚  β”‚ Service β”‚ β”‚ Service β”‚ β”‚ Service β”‚ β”‚ Service β”‚          β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚          β”‚           β”‚           β”‚
           β–Ό          β–Ό           β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PostgreSQL  β”‚ β”‚  Redis   β”‚ β”‚  Redis   β”‚ β”‚  SMTP    β”‚
β”‚   (Users,    β”‚ β”‚ (Tokens, β”‚ β”‚  (OTPs,  β”‚ β”‚ (MailHog)β”‚
β”‚   Sessions)  β”‚ β”‚  Cache)  β”‚ β”‚ Attempts)β”‚ β”‚          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ” Security Concepts Explained

1. Password Hashing with Salt

The Problem: If we store passwords as-is, a database breach exposes all passwords.

The Solution: Hash passwords with bcrypt + unique salt per user.

Password: "mypassword123"
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Salt: "a1b2c3d4e5f6..."  (random, unique)  β”‚
β”‚                                             β”‚
β”‚  Combined: "mypassword123" + salt           β”‚
β”‚                     β”‚                       β”‚
β”‚                     β–Ό                       β”‚
β”‚            bcrypt(combined, 12)             β”‚
β”‚                     β”‚                       β”‚
β”‚                     β–Ό                       β”‚
β”‚  Hash: "$2b$12$xyz..."  (stored in DB)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why Salt?

  • Without salt: Same password = same hash (vulnerable to rainbow tables)
  • With unique salt: Same password = different hash (secure!)

Code Location: src/services/hashService.js


2. JWT Access Tokens

The Problem: Checking the database for every API request is slow and doesn't scale.

The Solution: Issue a self-contained token that the server can verify without database lookups.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        JWT STRUCTURE                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                  β”‚
β”‚  Header.Payload.Signature                                        β”‚
β”‚                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   HEADER    β”‚  β”‚     PAYLOAD     β”‚  β”‚     SIGNATURE       β”‚   β”‚
β”‚  β”‚             β”‚  β”‚                 β”‚  β”‚                     β”‚   β”‚
β”‚  β”‚ {           β”‚  β”‚ {               β”‚  β”‚  HMAC-SHA256(       β”‚   β”‚
β”‚  β”‚   "alg":    β”‚  β”‚   "userId": 1,  β”‚  β”‚    header + "." +   β”‚   β”‚
β”‚  β”‚   "HS256",  β”‚  β”‚   "email": "x", β”‚  β”‚    payload,         β”‚   β”‚
β”‚  β”‚   "typ":    β”‚  β”‚   "role": "user"β”‚  β”‚    SECRET_KEY       β”‚   β”‚
β”‚  β”‚   "JWT"     β”‚  β”‚   "exp": 12345  β”‚  β”‚  )                  β”‚   β”‚
β”‚  β”‚ }           β”‚  β”‚ }               β”‚  β”‚                     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚        β”‚                   β”‚                      β”‚              β”‚
β”‚        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                            β”‚                                     β”‚
β”‚                     Base64 Encoded                               β”‚
β”‚                            β”‚                                     β”‚
β”‚   eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

How Verification Works (No Database!):

  1. Server receives JWT
  2. Server uses SECRET_KEY to verify signature
  3. If valid β†’ trust the payload data
  4. If invalid β†’ reject the request

Why Short Expiry (15 min)?

  • If stolen, attacker has limited time
  • Refresh token handles getting new access tokens

Code Location: src/services/tokenService.js


3. Refresh Token Rotation

The Problem: Short-lived access tokens = user logs out frequently. Long-lived tokens = security risk.

The Solution: Two-token system with rotation.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     REFRESH TOKEN ROTATION FLOW                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                         β”‚
β”‚  LOGIN                                                                  β”‚
β”‚  ─────                                                                  β”‚
β”‚  User β†’ [email + password] β†’ Server                                     β”‚
β”‚                                 β”‚                                       β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚
β”‚                    β–Ό                         β–Ό                          β”‚
β”‚            Access Token (JWT)        Refresh Token                      β”‚
β”‚            β”œβ”€ 15 min expiry          β”œβ”€ 7 day expiry                    β”‚
β”‚            β”œβ”€ Stored in memory       β”œβ”€ Stored in httpOnly cookie       β”‚
β”‚            └─ Used for API calls     └─ Hash stored in database         β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────────  β”‚
β”‚                                                                         β”‚
β”‚  REFRESH (Token Rotation)                                               β”‚
β”‚  ───────────────────────                                                β”‚
β”‚                                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    Old Refresh Token    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚  β”‚ Client  β”‚ ───────────────────────►│ Server  β”‚                        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜                        β”‚
β”‚       β–²                                   β”‚                             β”‚
β”‚       β”‚                         1. Verify hash in DB                    β”‚
β”‚       β”‚                         2. INVALIDATE old token ← Key step!     β”‚
β”‚       β”‚                         3. Generate NEW tokens                  β”‚
β”‚       β”‚                                   β”‚                             β”‚
β”‚       β”‚   New Access + Refresh Tokens     β”‚                             β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚
β”‚                                                                         β”‚
β”‚  ─────────────────────────────────────────────────────────────────────  β”‚
β”‚                                                                         β”‚
β”‚  THEFT DETECTION                                                        β”‚
β”‚  ───────────────                                                        β”‚
β”‚                                                                         β”‚
β”‚  If attacker uses STOLEN refresh token:                                 β”‚
β”‚                                                                         β”‚
β”‚  1. Attacker uses stolen token β†’ Gets new tokens β†’ Old token invalid    β”‚
β”‚  2. Real user uses OLD token β†’ Token already used! β†’ ALERT!             β”‚
β”‚  3. Server revokes ALL tokens for that user β†’ Forces re-login           β”‚
β”‚                                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Location: src/services/tokenService.js β†’ rotateRefreshToken()


4. OTP (One-Time Password)

The Problem: Passwords can be phished or leaked. We need a second factor.

The Solution: Send a temporary code via email that expires quickly.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       OTP FLOW                                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  1. GENERATE                                                    β”‚
β”‚     ─────────                                                   β”‚
β”‚     β€’ Generate 6-digit code: 847293                             β”‚
β”‚     β€’ Hash before storing (security!)                           β”‚
β”‚                                                                 β”‚
β”‚  2. STORE IN REDIS                                              β”‚
β”‚     ────────────────                                            β”‚
β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚     β”‚  Key: "otp:user@example.com:verify_email"  β”‚              β”‚
β”‚     β”‚  Value: "hashed_847293"                    β”‚              β”‚
β”‚     β”‚  TTL: 300 seconds (5 minutes)              β”‚ ← Auto-expireβ”‚
β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                                                                 β”‚
β”‚  3. SEND EMAIL                                                  β”‚
β”‚     ──────────                                                  β”‚
β”‚     πŸ“§ "Your code is: 847293"                                   β”‚
β”‚                                                                 β”‚
β”‚  4. VERIFY                                                      β”‚
β”‚     ──────                                                      β”‚
β”‚     User enters: 847293                                         β”‚
β”‚     Server: hash(847293) == stored_hash? βœ“                      β”‚
β”‚     Delete from Redis (one-time use)                            β”‚
β”‚                                                                 β”‚
β”‚  WHY REDIS?                                                     β”‚
β”‚  ──────────                                                     β”‚
β”‚  β€’ Auto-expiration (TTL) - no cleanup needed                    β”‚
β”‚  β€’ Fast read/write - no DB overhead                             β”‚
β”‚  β€’ Temporary data - perfect for in-memory cache                 β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Location: src/services/otpService.js


5. Google OAuth 2.0

The Problem: Users hate creating new passwords. They want "Login with Google."

The Solution: OAuth 2.0 - let Google verify the user, then create our own session.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      GOOGLE OAUTH 2.0 FLOW                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                         β”‚
β”‚     USER            AUTHFORGE              GOOGLE                       β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  1.   │──Click "Login   β”‚                     β”‚                         β”‚
β”‚       β”‚  with Google"──►│                     β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  2.   β”‚                 │──Redirect to────────►                         β”‚
β”‚       β”‚                 β”‚  Google consent     β”‚                         β”‚
β”‚       β”‚                 β”‚  screen             β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  3.   │◄────────────────────────────────────  β”‚                         β”‚
β”‚       β”‚  "AuthForge wants to access your      β”‚                         β”‚
β”‚       β”‚   email and profile. Allow?"          β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  4.   │──Click "Allow"──│─────────────────────►                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  5.   β”‚                 │◄──Redirect with ────│                         β”‚
β”‚       β”‚                 β”‚   ?code=abc123      β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  6.   β”‚                 │──Exchange code──────►  (Server-to-server)     β”‚
β”‚       β”‚                 β”‚  for tokens         β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  7.   β”‚                 │◄──{access_token, ───│                         β”‚
β”‚       β”‚                 β”‚    id_token}        β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  8.   β”‚                 │──Decode id_token────│                         β”‚
β”‚       β”‚                 β”‚  Get: email, name   β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚  9.   β”‚                 │──Find/Create user ──│                         β”‚
β”‚       β”‚                 β”‚  in OUR database    β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚ 10.   β”‚                 │──Issue OUR JWT ─────│                         β”‚
β”‚       β”‚                 β”‚  (not Google's!)    β”‚                         β”‚
β”‚       β”‚                 β”‚                     β”‚                         β”‚
β”‚ 11.   │◄──Logged in!────│                     β”‚                         β”‚
β”‚       β”‚  w/ AuthForge   β”‚                     β”‚                         β”‚
β”‚       β”‚  session        β”‚                     β”‚                         β”‚
β”‚                                                                         β”‚
β”‚  KEY INSIGHT: We use Google ONLY for identity verification.            β”‚
β”‚  We issue OUR OWN tokens - keeping us independent of Google.           β”‚
β”‚                                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Location: src/controllers/oauthController.js


6. Rate Limiting & Account Lockout

The Problem: Attackers can try thousands of passwords per second.

The Solution: Limit attempts and lock accounts after failures.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    PROTECTION LAYERS                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  LAYER 1: IP-BASED RATE LIMITING                                β”‚
β”‚  ───────────────────────────────                                β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                             β”‚
β”‚  β”‚ 192.168.1.100  │──► 5 login attempts / 15 min                β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚                                        β”‚
β”‚                        β”œβ”€ Attempt 1: βœ“                          β”‚
β”‚                        β”œβ”€ Attempt 2: βœ“                          β”‚
β”‚                        β”œβ”€ Attempt 3: βœ“                          β”‚
β”‚                        β”œβ”€ Attempt 4: βœ“                          β”‚
β”‚                        β”œβ”€ Attempt 5: βœ“                          β”‚
β”‚                        └─ Attempt 6: ❌ BLOCKED (429 Error)      β”‚
β”‚                                                                 β”‚
β”‚  ─────────────────────────────────────────────────────────────  β”‚
β”‚                                                                 β”‚
β”‚  LAYER 2: ACCOUNT LOCKOUT                                       β”‚
β”‚  ────────────────────────                                       β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                        β”‚
β”‚  β”‚ user@example.com    │──► Track failed attempts in Redis      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                        β”‚
β”‚                                                                 β”‚
β”‚  Attempt 1: Wrong password β†’ attempts: 1                        β”‚
β”‚  Attempt 2: Wrong password β†’ attempts: 2                        β”‚
β”‚  Attempt 3: Wrong password β†’ attempts: 3                        β”‚
β”‚  Attempt 4: Wrong password β†’ attempts: 4                        β”‚
β”‚  Attempt 5: Wrong password β†’ attempts: 5 β†’ πŸ”’ LOCKED            β”‚
β”‚                                                                 β”‚
β”‚  Account locked for 15 minutes                                  β”‚
β”‚  Even correct password won't work!                              β”‚
β”‚                                                                 β”‚
β”‚  ─────────────────────────────────────────────────────────────  β”‚
β”‚                                                                 β”‚
β”‚  LAYER 3: GENERAL API RATE LIMIT                                β”‚
β”‚  ───────────────────────────────                                β”‚
β”‚                                                                 β”‚
β”‚  All endpoints: 100 requests / 15 min per IP                    β”‚
β”‚  Prevents DDoS and API abuse                                    β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code Location: src/middleware/rateLimiter.js


πŸ“‘ API Reference

Authentication Endpoints

Method Endpoint Description Auth Required
POST /auth/register Create new account ❌
POST /auth/login Login with email/password ❌
POST /auth/verify-email Verify email with OTP ❌
POST /auth/refresh Get new access token πŸͺ Cookie
POST /auth/logout Logout & revoke tokens βœ…
POST /auth/forgot-password Request password reset ❌
POST /auth/reset-password Reset with OTP ❌
POST /auth/change-password Change password βœ…
GET /auth/me Get current user βœ…

OAuth Endpoints

Method Endpoint Description
GET /oauth/google Start Google login
GET /oauth/google/callback Google callback

User Endpoints

Method Endpoint Description Auth Required
GET /user/profile Get profile βœ…
PATCH /user/profile Update profile βœ…
GET /user/sessions List active sessions βœ…
DELETE /user/sessions/:id Revoke a session βœ…
POST /user/mfa/enable Enable MFA βœ…
POST /user/mfa/disable Disable MFA βœ…
GET /user/activity Get activity log βœ…

πŸ“ Project Structure

authforge/
β”œβ”€β”€ docker-compose.yml      # All services configuration
β”œβ”€β”€ Dockerfile              # Production API image
β”œβ”€β”€ Dockerfile.dev          # Development API image
β”‚
β”œβ”€β”€ frontend/               # React UI
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ pages/          # Login, Register, Dashboard
β”‚   β”‚   β”œβ”€β”€ App.jsx         # Routes & AuthContext
β”‚   β”‚   └── index.css       # Styling
β”‚   └── Dockerfile
β”‚
β”œβ”€β”€ prisma/
β”‚   └── schema.prisma       # Database schema
β”‚
└── src/                    # Backend
    β”œβ”€β”€ app.js              # Express app
    β”œβ”€β”€ config/
    β”‚   β”œβ”€β”€ database.js     # Prisma client
    β”‚   β”œβ”€β”€ redis.js        # Redis client
    β”‚   └── env.js          # Environment config
    β”œβ”€β”€ controllers/
    β”‚   β”œβ”€β”€ authController.js    # Login, register, etc.
    β”‚   β”œβ”€β”€ oauthController.js   # Google OAuth
    β”‚   └── userController.js    # Profile, sessions
    β”œβ”€β”€ middleware/
    β”‚   β”œβ”€β”€ authenticate.js      # JWT verification
    β”‚   β”œβ”€β”€ authorize.js         # Role-based access
    β”‚   β”œβ”€β”€ rateLimiter.js       # Rate limiting
    β”‚   └── errorHandler.js      # Error handling
    β”œβ”€β”€ services/
    β”‚   β”œβ”€β”€ hashService.js       # Password hashing
    β”‚   β”œβ”€β”€ tokenService.js      # JWT management
    β”‚   β”œβ”€β”€ otpService.js        # OTP generation
    β”‚   β”œβ”€β”€ emailService.js      # Send emails
    β”‚   └── auditService.js      # Audit logging
    └── routes/
        β”œβ”€β”€ auth.routes.js
        β”œβ”€β”€ oauth.routes.js
        └── user.routes.js

βš™οΈ Configuration

All configuration is in docker-compose.yml environment variables.

For Production, Update These:

# Generate secure secrets:
# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

- JWT_ACCESS_SECRET=<64-char-random-secret>
- JWT_REFRESH_SECRET=<64-char-random-secret>
- COOKIE_SECRET=<random-secret>

# Get from https://console.cloud.google.com/
- GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
- GOOGLE_CLIENT_SECRET=your-client-secret

# Use real SMTP (SendGrid, AWS SES, etc.)
- SMTP_HOST=smtp.sendgrid.net
- SMTP_PORT=587
- SMTP_USER=apikey
- SMTP_PASS=your-api-key

🚒 Deployment

Deploy to Railway/Render

  1. Push code to GitHub
  2. Connect repository to Railway/Render
  3. Set environment variables
  4. Deploy!

Deploy with Docker

# Build production images
docker-compose -f docker-compose.prod.yml build

# Run in production
docker-compose -f docker-compose.prod.yml up -d

πŸ§ͺ Testing the Flow

  1. Register: http://localhost:5173/register
  2. Check email: http://localhost:8025 (MailHog)
  3. Enter OTP: Complete verification
  4. Dashboard: Explore sessions and MFA
  5. Enable MFA: Toggle 2FA
  6. Logout & Login: See MFA in action!

πŸ“„ License

MIT Β© Piyush Sahoo


Built with ❀️ by Piyush

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages