Day 4 of 5
⏱ ~60 minutes
Auth and Security for Web Apps — Day 4

Rate Limiting and Brute Force Protection

Without rate limiting, your login endpoint is vulnerable to brute force attacks. Day 4 implements rate limiting, account lockout, and password security.

Rate Limiting Login Endpoints

Terminal
npm install express-rate-limit
rate-limit.js
const rateLimit = require('express-rate-limit');

// General API rate limit
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,                    // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later' }
});

// Strict limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // 5 attempts per 15 minutes
  skipSuccessfulRequests: true, // Don't count successful logins
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many login attempts. Try again in 15 minutes.'
    });
  }
});

app.use('/api/', apiLimiter);
app.use('/login', authLimiter);
app.use('/register', authLimiter);

Secure Password Hashing with bcrypt

Password Security
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12; // Higher = slower to crack, slower to compute

// On registration: hash the password
async function register(email, plainPassword) {
  const hash = await bcrypt.hash(plainPassword, SALT_ROUNDS);
  await User.create({ email, passwordHash: hash });
}

// On login: compare password to hash
async function login(email, plainPassword) {
  const user = await User.findByEmail(email);
  if (!user) {
    // Still compare to prevent timing attacks
    await bcrypt.compare(plainPassword, '$2b$12$invalid.hash.padding');
    return null;
  }
  
  const valid = await bcrypt.compare(plainPassword, user.passwordHash);
  return valid ? user : null;
}

// Password strength validation
function isStrongPassword(password) {
  return (
    password.length >= 12 &&
    /[A-Z]/.test(password) &&
    /[0-9]/.test(password) &&
    /[^A-Za-z0-9]/.test(password)
  );
}
Day 4 Exercise
Add Rate Limiting to Your App
  1. Add express-rate-limit and configure both the general API limiter and the strict auth limiter.
  2. Test the auth limiter: make 6 rapid POST requests to /login and verify the 6th is blocked.
  3. Verify that successful logins don't count toward the limit (skipSuccessfulRequests).
  4. Check that your registration flow hashes passwords with bcrypt before storing.

Day 4 Summary

  • Rate limit login endpoints strictly (5 attempts per 15 min) to prevent brute force.
  • Passwords must be hashed with bcrypt — never MD5, SHA-1, or plain text.
  • Always compare against a dummy hash even for non-existent users to prevent timing attacks.
Finished this lesson?