System Architecture Overview
Introduction
Cerberus IAM API is a comprehensive Identity and Access Management service built with Node.js, Express 4, and TypeScript 5. It provides OAuth 2.0/OIDC authentication, multi-tenancy, role-based access control (RBAC), and audit logging capabilities for the Cerberus platform.
Tech Stack
Core Technologies
- Runtime: Node.js 20+ (LTS)
- Framework: Express.js 4.19+
- Language: TypeScript 5.4+ (strict mode)
- Database: PostgreSQL (via Prisma ORM 5.22+)
- ORM: Prisma Client with migration tooling
Key Dependencies
Security & Authentication
- helmet (7.0+): Security headers middleware
- cors (2.8+): Cross-Origin Resource Sharing with allow-list
- argon2 (0.31+): Password hashing with Argon2id algorithm
- jose (5.2+): JWT signing and verification (EdDSA/RS256)
- csurf (1.11+): CSRF protection for session-based routes
Validation & Configuration
- zod (3.23+): Schema validation for environment and payloads
- dotenv (16.4+): Environment variable management
Logging & Observability
- pino (8.17+): High-performance structured logging
- pino-http (9.0+): HTTP request logging middleware
- morgan (1.10+): Legacy HTTP logger (being phased out)
Rate Limiting & Security
- rate-limiter-flexible (5.0+): Memory-based rate limiting with Redis support
- cookie-parser (1.4+): Cookie parsing for session management
MFA & Token Management
- otplib (12.0+): TOTP generation and verification
- uuid (9.0+): UUID v4 generation for IDs
Email & Notifications
- nodemailer (6.9+): SMTP email delivery for verification and password reset
Development Tools
- ESLint 9: Flat config with TypeScript support
- Prettier 3: Code formatting
- Husky: Git hooks for pre-commit quality checks
- lint-staged: Run linters on staged files only
- Jest 29: Testing framework with ts-jest
- Supertest: HTTP integration testing
- ts-node-dev: Development server with hot reload
Design Principles
1. Layered Architecture
┌─────────────────────────────────────────────┐
│ Client Applications │
│ (Admin Web, Mobile Apps, Third-party) │
└────────────────┬────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────┐
│ Middleware Pipeline │
│ Request ID → Helmet → Logger → CORS │
│ → Body Parser → Cookie Parser │
│ → Tenant → Auth → RBAC → CSRF │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Route Handlers │
│ /health, /.well-known, /oauth2, /v1 │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Controllers │
│ Request validation, response formatting │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Service Layer │
│ Business logic, domain operations │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Prisma Client (ORM) │
│ Type-safe database queries │
└────────────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ PostgreSQL Database │
│ Multi-tenant data storage │
└─────────────────────────────────────────────┘2. Multi-Tenancy
Cerberus IAM implements Organization-scoped multi-tenancy:
- Each
Organisationis the root tenant entity - All user operations are scoped to an organisation via
organisationId - Tenant context is established via
X-Org-Domainheader - Data isolation enforced at database query level using Prisma filters
// Tenant middleware extracts organisation context
req.tenant = {
id: organisation.id,
slug: organisation.slug,
organisation: Organisation,
};3. Strict Configuration Validation
Environment configuration is validated at startup using Zod schemas:
// src/config/index.ts
const configSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']),
DATABASE_URL: z.string().min(1),
JWT_ALG: z.enum(['EdDSA', 'RS256']),
SECRET_ENCRYPTION_KEY: z.string().min(44),
// ... more fields
});
// Process exits if validation fails
if (!parsedEnv.success) {
console.error('Invalid environment configuration');
process.exit(1);
}4. Pure Application Factory
The createApp() function in src/app.ts is pure - it returns an Express app without side effects like port binding. This enables:
- Easy testing with Supertest
- Multiple app instances for different environments
- Clean separation of app configuration and server lifecycle
// Pure factory
export const createApp = (): Application => {
const app = express();
// ... middleware setup
return app;
};
// Server binding happens separately
// src/server.ts
const app = createApp();
app.listen(config.PORT);5. Dependency Injection Ready
Services are instantiated as singletons but designed for future DI:
// Current pattern
export class UserService {
async findById(userId: string) {
/* ... */
}
}
export const userService = new UserService();
// Future DI pattern (ready)
export class UserService {
constructor(
private prisma: PrismaClient,
private auditService: AuditLogService,
) {}
}6. Error Handling Strategy
- Problem Details (RFC 7807): Structured error responses
- Central error handler: Catches all unhandled errors
- Domain-specific errors:
UnauthorizedError,ForbiddenError, etc. - Logging: Errors logged with request context using Pino
// utils/problem.ts
export const unauthorized = (detail: string): Problem => ({
type: 'https://api.cerberus-iam.com/errors/unauthorized',
title: 'Unauthorized',
status: 401,
detail,
});7. Security-First Design
- Helmet: Security headers enabled by default
- CORS: Strict origin allow-list
- Rate Limiting: Multiple limiters (global, auth, token)
- CSRF Protection: Token-based for session endpoints
- Password Hashing: Argon2id with strong parameters
- Secret Encryption: AES-256-GCM for sensitive data
- JWT Signing: EdDSA (Ed25519) or RS256
- Token Storage: Refresh tokens hashed with SHA-256
8. Observability
- Structured Logging: Pino JSON logs with request IDs
- Request Tracing: Every request gets a unique ID
- Audit Logging: Critical operations logged to
audit_logstable - Remote Log Export: Optional batched log shipping to external systems
// Every log entry includes:
{
service: 'cerberus-iam-api',
environment: 'production',
requestId: 'uuid-v4',
level: 'info',
msg: 'User logged in',
userId: '...',
organisationId: '...'
}Key Architectural Patterns
Request Context Propagation
Request-scoped data flows through middleware chain:
Request {
id: string; // Request ID (from requestIdMiddleware)
tenant?: TenantContext; // Organisation context (from tenantMiddleware)
user?: User; // Authenticated user (from authn middleware)
authOrganisation?: Organisation; // Auth'd org (from authn middleware)
apiKeyScopes?: string[]; // API key permissions
}Service Layer Pattern
Business logic lives in service classes:
class UserService {
async findById(userId: string, organisationId: string): Promise<User | null>;
async create(data: CreateUserData, actorId?: string): Promise<User>;
async update(userId: string, data: UpdateUserData): Promise<User>;
async softDelete(userId: string): Promise<void>;
}Middleware Factories
Reusable middleware with configuration:
export function requirePerm(permission: string) {
return async (req, res, next) => {
// Permission check logic
if (!hasPermission) {
sendProblem(res, forbidden('Missing permission'));
return;
}
next();
};
}
// Usage
router.get('/admin/users', requirePerm('users:read'), handler);Token Family Tracking
Refresh token rotation with reuse detection:
RefreshToken {
familyId: string; // Links all tokens in rotation chain
parentTokenId?: string; // Previous token in chain
children: RefreshToken[]; // Next token(s) in chain
revokedAt?: Date; // Revoked if reused or expired
}
// Reuse detection algorithm:
// 1. Check if token has children (already rotated)
// 2. If yes → revoke entire family (compromise detected)
// 3. If no → create new token with current as parentModule Organization
src/
├── app.ts # Express app factory
├── server.ts # Server entry point
├── config/ # Configuration & environment
│ └── index.ts
├── middleware/ # Express middleware
│ ├── requestId.ts
│ ├── authn.ts
│ ├── tenant.ts
│ ├── rbac.ts
│ ├── csrf.ts
│ ├── rateLimit.ts
│ └── apikey.ts
├── routes/ # Route definitions
│ ├── index.ts # Health check
│ ├── well-known/ # OIDC discovery
│ ├── oauth2/ # OAuth 2.0 endpoints
│ └── api/v1/ # REST API
├── controllers/ # Request handlers
├── services/ # Business logic
│ ├── user.service.ts
│ ├── token.service.ts
│ ├── organisation.service.ts
│ └── ...
├── auth/ # Auth utilities
│ ├── jwt.ts # JWT signing/verification
│ ├── password.ts # Password hashing
│ ├── sessions.ts # Session management
│ └── mfa.ts # MFA/TOTP
├── db/ # Database
│ └── prisma.ts # Prisma client singleton
├── utils/ # Utilities
│ ├── crypto.ts # Cryptographic functions
│ ├── problem.ts # RFC 7807 errors
│ └── audit.ts # Audit logging helpers
├── types/ # TypeScript types
└── logger.ts # Pino logger setupMulti-Environment Support
Development
- Hot reload via
ts-node-dev - Detailed error messages
- Source maps enabled
- Pretty-printed logs
Testing
- Isolated Jest projects (unit, integration, e2e)
- Mock Prisma client
- Ephemeral test databases
- Parallel test execution
Production
- Compiled to JavaScript via
tsc - Structured JSON logs
- Error details suppressed
- Health check endpoint for load balancers
Deployment Architecture
┌──────────────────────────────────────────────────┐
│ Load Balancer / CDN │
│ (AWS ALB / CloudFront) │
└────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ API Container (ECS/EKS) │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ Node.js 20 + Express │ │
│ │ - Health check: /health │ │
│ │ - Graceful shutdown │ │
│ │ - Request timeout handling │ │
│ └────────────────────────────────────────┘ │
└────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ PostgreSQL (RDS/Aurora) │
│ - Multi-AZ for HA │
│ - Connection pooling │
│ - Automated backups │
└──────────────────────────────────────────────────┘Scalability Considerations
- Stateless Design: No in-memory session storage; all state in DB
- Horizontal Scaling: Multiple API instances behind load balancer
- Database Connection Pooling: Prisma manages connection pool
- Rate Limiting: Can be migrated to Redis for distributed limiting
- Caching: Ready for Redis integration for JWK caching
Future Enhancements
Planned Features
- [ ] Redis-backed rate limiting for multi-instance deployments
- [ ] JWK caching with automatic key rotation
- [ ] Webhook event delivery with retry logic
- [ ] SAML SSO integration
- [ ] Social OAuth providers (Google, GitHub, etc.)
- [ ] Advanced audit log querying and export
- [ ] Real-time activity monitoring dashboard
Technical Debt
- [ ] Replace Morgan with Pino HTTP logger entirely
- [ ] Implement comprehensive API documentation (OpenAPI/Swagger)
- [ ] Add end-to-end encryption for sensitive user data
- [ ] Improve test coverage to >90%
- [ ] Add performance benchmarks and load testing