feature/domain-migration main AuthForge Case Study | Clifford Opoku-Sarkodie - Backend Engineer
Security

AuthForge

Enterprise-Grade Authentication Microservice

2024 TypeScript, Node.js, JWT Production Ready

Project Overview

The Problem

Every new microservice was reinventing authentication—scattered JWT logic, inconsistent password policies, and zero visibility into access patterns across the platform.

The Solution

A centralized authentication microservice with enterprise-grade security: JWT with refresh token rotation, fine-grained RBAC, and comprehensive audit logging.

Key Metrics

  • 🔐 Zero authentication vulnerabilities in security audit
  • ⚡ Sub-10ms token validation latency
  • 📊 100% API call traceability via audit logs

System Architecture

High-level overview of the authentication flow and security layers.

flowchart TB subgraph Client["Client Applications"] WEB[Web App] MOBILE[Mobile App] API_CLIENT[API Client] end subgraph Gateway["API Gateway"] AUTH_MW[Auth Middleware] RATE[Rate Limiter] end subgraph AuthForge["AuthForge Service"] LOGIN[Login Handler] REGISTER[Registration] TOKEN[Token Service] RBAC[RBAC Engine] AUDIT[Audit Logger] end subgraph Security["Security Layer"] HASH[Password Hasher] JWT_SIGN[JWT Signer] REFRESH[Refresh Token Manager] end subgraph Data["Data Layer"] POSTGRES[(PostgreSQL)] REDIS[(Redis Cache)] end WEB --> AUTH_MW MOBILE --> AUTH_MW API_CLIENT --> AUTH_MW AUTH_MW --> RATE RATE --> LOGIN RATE --> REGISTER LOGIN --> HASH LOGIN --> TOKEN REGISTER --> HASH TOKEN --> JWT_SIGN TOKEN --> REFRESH TOKEN --> RBAC LOGIN --> AUDIT REGISTER --> AUDIT TOKEN --> AUDIT HASH --> POSTGRES RBAC --> POSTGRES AUDIT --> POSTGRES REFRESH --> REDIS TOKEN --> REDIS

Database Schema

Entity-Relationship diagram showing the RBAC and session management models.

erDiagram USERS ||--o{ USER_ROLES : has USERS ||--o{ SESSIONS : owns USERS ||--o{ AUDIT_LOGS : generates ROLES ||--o{ USER_ROLES : assigned ROLES ||--o{ ROLE_PERMISSIONS : grants PERMISSIONS ||--o{ ROLE_PERMISSIONS : belongs USERS { uuid id PK string email UK string password_hash string name boolean is_active boolean email_verified timestamp created_at timestamp last_login } ROLES { uuid id PK string name UK string description boolean is_system timestamp created_at } PERMISSIONS { uuid id PK string resource string action string description } USER_ROLES { uuid user_id FK uuid role_id FK timestamp assigned_at } ROLE_PERMISSIONS { uuid role_id FK uuid permission_id FK } SESSIONS { uuid id PK uuid user_id FK string refresh_token_hash string ip_address string user_agent timestamp expires_at timestamp created_at } AUDIT_LOGS { uuid id PK uuid user_id FK string action string resource json metadata string ip_address timestamp created_at }

Engineering Challenges

01

Secure Token Refresh Flow

Problem

Refresh tokens are high-value targets. A stolen token could grant indefinite access if not properly managed.

Solution

Implemented refresh token rotation—each use generates a new token and invalidates the old one. Tokens are hashed before storage and tied to device fingerprints.

// Refresh token rotation with reuse detection
async function rotateRefreshToken(oldToken: string) {
  const session = await redis.get(`refresh:${hash(oldToken)}`);
  
  if (!session) {
    // Potential token reuse attack - invalidate all user sessions
    await invalidateAllUserSessions(session.userId);
    throw new SecurityError('Token reuse detected');
  }
  
  // Generate new token pair
  const newRefresh = generateSecureToken();
  await redis.del(`refresh:${hash(oldToken)}`);
  await redis.setex(`refresh:${hash(newRefresh)}`, REFRESH_TTL, session);
  
  return { accessToken: signJWT(session), refreshToken: newRefresh };
}
02

Fine-Grained RBAC at Scale

Problem

Permission checks on every request added latency. Complex role hierarchies made the logic hard to maintain.

Solution

Pre-computed permission sets cached in Redis with TTL. Role changes trigger cache invalidation. Permission checks become O(1) hash lookups.

// Cached permission check with lazy loading
async function hasPermission(userId: string, resource: string, action: string) {
  const cacheKey = `perms:${userId}`;
  let permissions = await redis.smembers(cacheKey);
  
  if (!permissions.length) {
    // Cache miss - compute and cache permissions
    permissions = await computeEffectivePermissions(userId);
    await redis.sadd(cacheKey, ...permissions);
    await redis.expire(cacheKey, PERMISSION_CACHE_TTL);
  }
  
  return permissions.includes(`${resource}:${action}`);
}
03

Password Policy Enforcement

Problem

Users chose weak passwords. Password history wasn't tracked, allowing reuse of compromised credentials.

Solution

Implemented zxcvbn strength scoring, breach database checks via k-anonymity (HaveIBeenPwned API), and password history with configurable depth.

// Multi-layer password validation
async function validatePassword(password: string, userId?: string) {
  // Strength check
  const strength = zxcvbn(password);
  if (strength.score < MIN_STRENGTH) {
    throw new ValidationError(strength.feedback.warning);
  }
  
  // Breach check via k-anonymity
  const isBreached = await checkHIBP(password);
  if (isBreached) {
    throw new ValidationError('Password found in data breach');
  }
  
  // History check (if updating)
  if (userId) {
    const history = await getPasswordHistory(userId, HISTORY_DEPTH);
    for (const oldHash of history) {
      if (await bcrypt.compare(password, oldHash)) {
        throw new ValidationError('Cannot reuse recent passwords');
      }
    }
  }
}

Key Takeaways

🔑

Defense in Depth

Never rely on a single security layer. Combine JWT validation, rate limiting, device fingerprinting, and anomaly detection.

📝

Audit Everything

Every authentication event is logged with context. Invaluable for incident response and compliance requirements.

🚀

Cache Strategically

Permission lookups happen on every request. Pre-computing and caching reduced auth overhead from 50ms to under 5ms.

🔄

Token Rotation is Essential

Refresh token rotation with reuse detection catches replay attacks and limits the damage window of stolen tokens.

Interested in the Technical Details?

Check out the full source code or reach out to discuss the architecture.