How to Protect Against Brute Force Attacks
Brute force attacks try many combinations to guess passwords, API keys, or tokens. Without protection, attackers can crack weak passwords in minutes. This guide covers rate limiting, progressive delays, account lockout, and CAPTCHA implementation.
Find security issues automatically before attackers do.
Follow These Steps
Implement rate limiting on authentication endpoints
Limit login attempts per IP and per account.
import rateLimit from 'express-rate-limit'
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
keyGenerator: (req) => `${req.ip}-${req.body?.email || 'unknown'}`,
message: { error: 'Too many login attempts. Try again in 15 minutes.' }
})
app.post('/api/auth/login', loginLimiter, handleLogin)Add progressive delays
Increase wait time after each failed attempt.
const failedAttempts = new Map<string, { count: number; lastAttempt: number }>()
function getDelay(email: string): number {
const record = failedAttempts.get(email)
if (!record) return 0
// Exponential backoff: 0, 1s, 2s, 4s, 8s, 16s...
return Math.min(Math.pow(2, record.count - 1) * 1000, 30000)
}
async function login(email: string, password: string) {
const delay = getDelay(email)
if (delay > 0) await new Promise(r => setTimeout(r, delay))
const user = await verifyCredentials(email, password)
if (!user) {
const record = failedAttempts.get(email) || { count: 0, lastAttempt: 0 }
failedAttempts.set(email, { count: record.count + 1, lastAttempt: Date.now() })
throw new Error('Invalid credentials')
}
failedAttempts.delete(email)
return user
}Implement account lockout
Temporarily lock accounts after too many failed attempts.
const MAX_ATTEMPTS = 5
const LOCKOUT_DURATION = 30 * 60 * 1000 // 30 minutes
async function checkLockout(email: string): boolean {
const user = await db.query.users.findFirst({
where: eq(users.email, email)
})
if (!user) return false
if (user.failedAttempts >= MAX_ATTEMPTS) {
const lockoutEnd = new Date(user.lastFailedAt).getTime() + LOCKOUT_DURATION
if (Date.now() < lockoutEnd) {
return true // Account is locked
}
// Lockout expired, reset counter
await db.update(users).set({ failedAttempts: 0 }).where(eq(users.id, user.id))
}
return false
}Add CAPTCHA after failed attempts
Show a CAPTCHA challenge after a few failed attempts.
// After 3 failed attempts, require CAPTCHA
if (failedCount >= 3) {
const captchaToken = req.body.captchaToken
if (!captchaToken) {
return res.status(400).json({ error: 'CAPTCHA required', requiresCaptcha: true })
}
// Verify with reCAPTCHA
const response = await fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: process.env.RECAPTCHA_SECRET!,
response: captchaToken
})
})
const result = await response.json()
if (!result.success) {
return res.status(400).json({ error: 'CAPTCHA verification failed' })
}
}Use strong password requirements
Enforce password policies that make brute force impractical.
const PasswordPolicy = z.string()
.min(12, 'Password must be at least 12 characters')
.max(128)
// Optionally check against common passwords
.refine(
(pw) => !commonPasswords.includes(pw.toLowerCase()),
'This password is too common'
)Monitor and alert on suspicious activity
Log failed login attempts and alert on unusual patterns.
// Log all failed attempts
console.warn(`Failed login: ${email} from ${ip} at ${new Date().toISOString()}`)
// Alert on multiple failures across different accounts from same IP
if (ipFailCount > 20) {
alertSecurityTeam(`Possible brute force from IP ${ip}: ${ipFailCount} failures in 15 min`)
}What You'll Achieve
Login endpoints are rate limited, progressive delays slow down attackers, accounts lock after repeated failures, CAPTCHA challenges block automated attacks, and monitoring detects suspicious patterns.
Common Mistakes to Avoid
Mistake
Only rate limiting by IP
Fix
Rate limit by both IP and account email. Attackers can rotate IPs. Account-level limiting catches targeted attacks.
Mistake
Permanently locking accounts
Fix
Permanent lockout is a denial-of-service vector. Use temporary lockout (15-30 minutes) that automatically expires.
Frequently Asked Questions
How many login attempts should I allow?
5 attempts per 15 minutes is a common configuration. After 5 failures, lock the account for 15-30 minutes and require CAPTCHA.
Can brute force attack my API endpoints?
Yes. Any endpoint that accepts input (search, password reset, API keys) can be brute forced. Apply rate limiting to all endpoints, with stricter limits on sensitive ones.
Ready to Secure Your App?
VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.
Start Security Scan