Broken User Authentication
Broken User Authentication
Authentication vulnerabilities allow attackers to compromise user accounts or bypass authentication entirely. Common issues include weak password policies, lack of account lockout mechanisms, predictable tokens, and missing multi-factor authentication. These vulnerabilities often combine to create severe security risks.
// Node.js example: Secure authentication implementation
const bcrypt = require('bcrypt');
const speakeasy = require('speakeasy');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class SecureAuthenticationService {
constructor(db, redis, config) {
this.db = db;
this.redis = redis;
this.config = config;
}
// VULNERABLE: Weak authentication
async authenticateVulnerable(username, password) {
const user = await this.db.users.findOne({ username });
// Direct password comparison - NO!
if (user && user.password === password) {
return { token: jwt.sign({ userId: user.id }, 'secret') };
}
return null;
}
// SECURE: Comprehensive authentication
async authenticate(username, password, mfaToken = null) {
// Input validation
if (!this.isValidUsername(username) || !password) {
throw new Error('Invalid credentials');
}
// Check rate limiting
const attempts = await this.checkRateLimit(username);
if (attempts >= 5) {
await this.notifySecurityTeam('Brute force attempt', { username });
throw new Error('Account temporarily locked');
}
// Fetch user with timing-safe comparison
const user = await this.db.users.findOne({
username: username.toLowerCase()
});
// Always perform password check to prevent timing attacks
const dummyHash = '$2b$10$dummy.hash.to.prevent.timing.attacks';
const passwordHash = user ? user.passwordHash : dummyHash;
const isValidPassword = await bcrypt.compare(password, passwordHash);
if (!user || !isValidPassword) {
await this.incrementFailedAttempts(username);
// Generic error message
throw new Error('Invalid credentials');
}
// Check if account is active
if (user.status !== 'active') {
throw new Error('Account is not active');
}
// Check for suspicious activity
await this.checkSuspiciousActivity(user);
// Verify MFA if enabled
if (user.mfaEnabled) {
if (!mfaToken) {
return {
requiresMfa: true,
sessionToken: this.generateSessionToken(user.id)
};
}
const isValidMfa = this.verifyMfaToken(user.mfaSecret, mfaToken);
if (!isValidMfa) {
await this.incrementFailedAttempts(username);
throw new Error('Invalid MFA token');
}
}
// Generate tokens
const accessToken = this.generateAccessToken(user);
const refreshToken = await this.generateRefreshToken(user);
// Update login metadata
await this.updateLoginMetadata(user.id, {
lastLoginAt: new Date(),
lastLoginIp: this.getClientIp(),
loginCount: user.loginCount + 1
});
// Clear failed attempts
await this.clearFailedAttempts(username);
return {
accessToken,
refreshToken,
expiresIn: 3600,
user: this.sanitizeUser(user)
};
}
async checkRateLimit(username) {
const key = `auth_attempts:${username}`;
const attempts = await this.redis.incr(key);
if (attempts === 1) {
await this.redis.expire(key, 900); // 15 minutes
}
return attempts;
}
generateAccessToken(user) {
const payload = {
sub: user.id,
username: user.username,
roles: user.roles,
permissions: user.permissions,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600 // 1 hour
};
return jwt.sign(payload, this.config.jwtSecret, {
algorithm: 'RS256',
issuer: this.config.issuer,
audience: this.config.audience
});
}
async generateRefreshToken(user) {
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto
.createHash('sha256')
.update(token)
.digest('hex');
// Store hashed token
await this.db.refreshTokens.insert({
userId: user.id,
tokenHash: hashedToken,
createdAt: new Date(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
metadata: {
userAgent: this.getUserAgent(),
ip: this.getClientIp()
}
});
return token;
}
// Password reset with security measures
async initiatePasswordReset(email) {
const user = await this.db.users.findOne({ email: email.toLowerCase() });
// Always return success to prevent email enumeration
const response = { message: 'If the email exists, a reset link has been sent' };
if (!user) {
// Log attempt for security monitoring
await this.logSecurityEvent('password_reset_invalid_email', { email });
return response;
}
// Check rate limiting for password resets
const resetKey = `password_reset:${user.id}`;
const recentReset = await this.redis.get(resetKey);
if (recentReset) {
// Silently ignore if recent reset was requested
return response;
}
// Generate secure reset token
const resetToken = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
// Store reset token with expiration
await this.db.passwordResets.insert({
userId: user.id,
tokenHash: hashedToken,
createdAt: new Date(),
expiresAt: new Date(Date.now() + 3600000), // 1 hour
used: false
});
// Set rate limit
await this.redis.setex(resetKey, 3600, '1');
// Send reset email
await this.emailService.sendPasswordResetEmail(user.email, resetToken);
return response;
}
}