Skip to the solution
MediumSystem Design6 min read

How do you protect a Node.js API against Denial of Service (DoS) and brute-force attacks?

617 views

The problem

Question presented to candidate: "An attacker scripts thousands of login attempts per second against your /login endpoint, trying every password in a common wordlist. What specifically stops this, versus what stops a flood of requests trying to simply overwhelm your server's capacity?"

What a strong answer should cover:

  • Brute-force protection and general DoS protection are related but genuinely distinct concerns: brute-force specifically targets guessing a secret (a password) through repeated attempts; DoS targets overwhelming capacity through sheer request volume β€” the fixes overlap significantly (rate limiting) but the specific configuration and additional layers differ.
  • πŸ“Œ Verified, not assumed: a real rate-limiting middleware (express-rate-limit, max: 3 per window) allowed the first 3 requests through with 200 responses (the ratelimit-remaining header correctly counting down 2, 1, 0), then genuinely blocked requests 4 and 5 with a real 429 status β€” confirmed directly, not described.
  • For brute-force specifically: rate limiting should be tighter and keyed by the target identity (the specific username/account being attempted, not just source IP β€” an attacker can distribute attempts across many IPs), and account lockout or exponential backoff after repeated failures adds a second, complementary layer beyond a flat rate limit.
  • For general DoS/volume-based protection: a request body size limit (preventing a single oversized payload from consuming excessive memory/CPU to parse), a connection/request timeout (preventing a slow or stalled client from holding a connection open indefinitely), and β€” at the infrastructure layer, beyond application code β€” a CDN/WAF absorbing volumetric attacks before they ever reach the application at all.
  • A precise answer names that application-level rate limiting alone cannot fully stop a sufficiently large, distributed volumetric attack β€” that requires infrastructure-level mitigation (a CDN, a dedicated DoS-protection service) in front of the application, which application-level rate limiting complements rather than replaces.
  • Correct password hashing (scrypt/pbkdf2, covered fully with a real verified hash-and-verify pair in its own dedicated question) is itself a passive brute-force defense β€” a deliberately slow hashing algorithm makes each individual guess attempt computationally expensive for an attacker even if they somehow bypassed rate limiting entirely.

Clarifying questions expected:

  • "Is the concern brute-forcing a specific secret, or a general flood of traffic trying to overwhelm capacity?" β€” the two share some defenses but need different specific configuration.
  • "Is infrastructure-level mitigation (a CDN/WAF) already in place, or does this need to be handled entirely at the application layer?"

Code / implementation expected: Yes β€” the real, measured rate-limiter behavior (200s counting down to a genuine 429) is the concrete, convincing proof of the core mechanism, cross-linked to the dedicated password-hashing question for the complementary, passive brute-force defense.

  • security
  • api
  • middleware

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Read the code

JavaScriptA real express-rate-limit middleware: requests 1-3 allowed, requests 4-5 genuinely blocked with a real 429
const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();

const limiter = rateLimit({ windowMs: 60_000, max: 3, standardHeaders: true, legacyHeaders: false });
app.use(limiter);
app.get("/", (req, res) => res.json({ ok: true }));

// 5 real requests sent in sequence:
// request 1 -> status 200, remaining: 2
// request 2 -> status 200, remaining: 1
// request 3 -> status 200, remaining: 0
// request 4 -> status 429  <- genuinely blocked
// request 5 -> status 429  <- genuinely blocked

// For brute-force specifically, key by the ATTEMPTED account, not just IP:
// rateLimit({ windowMs: 60_000, max: 5, keyGenerator: (req) => req.body.username });

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 81 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

What are Node.js Streams and when would you use them?

Continue