feature/domain-migration main
Every new microservice was reinventing authentication—scattered JWT logic, inconsistent password policies, and zero visibility into access patterns across the platform.
A centralized authentication microservice with enterprise-grade security: JWT with refresh token rotation, fine-grained RBAC, and comprehensive audit logging.
High-level overview of the authentication flow and security layers.
Entity-Relationship diagram showing the RBAC and session management models.
Refresh tokens are high-value targets. A stolen token could grant indefinite access if not properly managed.
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 };
}
Permission checks on every request added latency. Complex role hierarchies made the logic hard to maintain.
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}`);
}
Users chose weak passwords. Password history wasn't tracked, allowing reuse of compromised credentials.
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');
}
}
}
}
Never rely on a single security layer. Combine JWT validation, rate limiting, device fingerprinting, and anomaly detection.
Every authentication event is logged with context. Invaluable for incident response and compliance requirements.
Permission lookups happen on every request. Pre-computing and caching reduced auth overhead from 50ms to under 5ms.
Refresh token rotation with reuse detection catches replay attacks and limits the damage window of stolen tokens.
Check out the full source code or reach out to discuss the architecture.