Wanted to learn about auth systems and slowly turned it into a complete auth with session handling, CSRF protection, Redis-backed token management, OTP login verification, email verification, caching, rate limiting, and security-focused architecture.
- Hybrid JWT + Redis session authentication
- Login with password + Email with OTP verification
- CSRF protection using double submit cookie pattern
- HTTP-only secure cookies
- Mongo injection protection
- Password hashing with bcrypt
- User caching in Redis
- Role-based authorization
- IP + email based rate limiting
- Logout with full session revocation
- Refresh token validation & session tracking
flowchart LR
Client[Client]
API[Express API]
Redis[(Redis)]
Mongo[(MongoDB)]
SMTP[SMTP Email Service]
Client -->|HTTP Requests/Cookies| API
API -->|Cache / Sessions / OTP / Tokens| Redis
API -->|Persistent User Data| Mongo
API -->|Verification & OTP Emails| SMTP
subgraph Security Layer
JWT[JWT Access + Refresh]
CSRF[CSRF Protection]
Rate[Rate Limiting]
RBAC[Role Authorization]
end
API --> JWT
API --> CSRF
API --> Rate
API --> RBAC
sequenceDiagram
participant U as User
participant A as API (Express)
participant R as Redis (Cache)
participant M as MongoDB
participant S as SMTP (Gmail)
U->>A: Register (name, email, password)
A->>A: Validate & Sanitize Input
A->>M: Check Existing User
alt User already exists
M-->>A: User Found
A-->>U: Email already in use
else User valid
A->>A: Hash Password (bcrypt)
A->>A: Generate Verification Token
A->>R: Store temporary user data + token
A->>S: Send Verification Email
end
U->>A: Click Verification Link
A->>R: Verify token
alt Token expired
R-->>A: Token Missing
A-->>U: Verification expired
else Token valid
A->>M: Create User Account
A->>R: Delete temporary token data
A-->>U: Account created & Email verified
end
sequenceDiagram
participant User
participant API
participant Redis
participant MongoDB
User->>API: Login(email, password)
API->>API: Validate & sanitize payload
API->>API: Apply rate limiting
API->>MongoDB: Find user
API->>API: Verify bcrypt password
API->>API: Generate OTP
API->>Redis: Store OTP with expiry
API->>API: Send OTP email via SMTP
User->>API: Verify(email, otp)
API->>Redis: Validate OTP
API->>Redis: Delete OTP after usage
API->>API: Generate access & refresh token
API->>API: Generate CSRF token
API->>Redis: Store session & refresh token
API-->>User: Set secure cookies
sequenceDiagram
participant Client
participant API
participant Redis
Client->>API: Request Protected Route
API->>API: Verify accessToken JWT
alt Access Token Valid
API->>Redis: Validate active_session:userId
alt Session Active
API-->>Client: Return Protected Data
else Session Revoked
API-->>Client: Clear Cookies + 401
end
else Access Token Expired
Client->>API: POST /refresh
API->>API: Verify refreshToken JWT
API->>Redis: Get refresh_token:userId
API->>Redis: Get active_session:userId
API->>Redis: Get session:sessionId
alt Refresh + Session Valid
API->>Redis: Update session lastActivity
API->>API: Generate New Access Token
API-->>Client: Set New accessToken Cookie
else Invalid Session
API-->>Client: Clear Cookies
API-->>Client: 401 Session Expired
end
end
sequenceDiagram
participant C as Client
participant A as API Middleware
participant R as Redis
Note over C, R: CSRF Token issued during Login/Refresh
C->>A: Request (POST/PUT/DELETE)
Note right of C: Header: x-csrf-token <br/> Cookie: csrfToken
A->>A: Extract userId from JWT
A->>R: GET csrf:userId
R-->>A: Stored Token
alt Token Matches
A->>A: next()
else Token Missing/Invalid
A-->>C: 403 Forbidden (CSRF Error)
end
stateDiagram-v2
[*] --> LoggedOut
LoggedOut --> LoginInitiated : Password Verified
LoginInitiated --> OTPPending : OTP Sent
OTPPending --> SessionCreated : OTP Verified
SessionCreated --> ActiveSession
ActiveSession --> AccessExpired : Access Token Expires
AccessExpired --> ActiveSession : Refresh Successful
ActiveSession --> LogoutRequested : User Logout
LogoutRequested --> SessionRevoked
ActiveSession --> ForcedLogout : Login From Another Device
ForcedLogout --> SessionRevoked
SessionRevoked --> LoggedOut
This project uses hybrid JWT + Redis session validation instead of fully stateless JWT authentication.
JWTs are used for authentication transport while Redis acts as the source of truth for active sessions.
This allows:
- session revocation
- forced logout from another device
- refresh token invalidation
- active session tracking
- short-lived access tokens with secure refresh flow
Redis handles OTP expiration, temporary email verification state, session validation, refresh token revocation, CSRF token storage and rate limiting
verify:token
otp:email
refresh_token:userId
active_session:userId
session:sessionId
csrf:userId
user:userId
login-rate-limit:ip:email
register-rate-limit:ip:email- Access Token Expiry: 15 minutes
- Refresh Token Expiry: 7 days
- OTP Expiry: 5 minutes
- CSRF Token Expiry: 1 hour
- Session Storage: Redis
git clone https://github.com/Baisayan/authy.gitnpm installCreate .env
PORT=5000
MONGO_URI=your_mongodb_uri
REDIS_URL=your_redis_url
SMTP_USER=your_email
SMTP_PASSWORD=your_app_password
JWT_SECRET=your_jwt_secret
REFRESH_SECRET=your_refresh_secret
npm run devNow, you can start testing endpoints with Postman.
POST: /api/v1/register
{
"name": "ved",
"email": "ved@mail.com",
"password": "password123"
}Check email for verification link.
- Open link from email. Account gets created.
POST: /api/v1/login
{
"email": "ved@mail.com",
"password": "password123"
}OTP gets generated, stored in Redis with expiry, and sent via email.
POST: /api/v1/verify
{
"email": "ved@example.com",
"otp": "123456"
}After success, cookies get set and session gets created
GET: /api/v1/me
- Should return user data and session info
POST: /api/v1/refresh
- Should generate new access token
POST: /api/v1/logout
- Send CSRF token in headers:
x-csrf-token: <csrf_token> - Should clear cookies, revoke session and delete refresh token