Step-by-Step Guide
6 steps

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

1

Implement rate limiting on authentication endpoints

Limit login attempts per IP and per account.

Code Example
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)
2

Add progressive delays

Increase wait time after each failed attempt.

Code Example
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
}
3

Implement account lockout

Temporarily lock accounts after too many failed attempts.

Code Example
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
}
4

Add CAPTCHA after failed attempts

Show a CAPTCHA challenge after a few failed attempts.

Code Example
// 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' })
  }
}
5

Use strong password requirements

Enforce password policies that make brute force impractical.

Code Example
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'
  )
6

Monitor and alert on suspicious activity

Log failed login attempts and alert on unusual patterns.

Code Example
// 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