Back to Security in Microservices
Q2Question 2 of 4

How do you secure sensitive endpoints from brute-force attacks

Key Concepts
  • Defence in depth: no single control stops brute force; layer rate limiting, lockout, CAPTCHA, MFA, and monitoring.
  • Rate limiting caps attempts per time window per account and per IP at the API gateway.
  • Account lockout temporarily disables after repeated failures, with progressive delays to avoid DoS on legitimate users.
  • CAPTCHA challenges automated scripts after a few failed attempts without annoying legitimate users on first login.
  • MFA is the strongest single control: a guessed password is useless without the second factor.
  • Monitoring and alerting turn silent attacks into visible ones by tracking failure rates and lockout spikes.
  • Short-lived tokens limit the blast radius if a credential is eventually guessed or stolen.

Definition

Stop brute force with layers: rate-limit attempts, lock accounts after repeated failures, challenge with CAPTCHA, require MFA so a password alone is useless, blacklist abusive IPs, monitor and alert on patterns, and keep tokens short-lived.
  • Rate limiting – Cap attempts per time window per account and per IP, enforced at the API gateway.
  • Account lockout – Temporarily disable an account after too many failed logins; use progressive delays to avoid enabling DoS.
  • CAPTCHA – A human-verification challenge shown after a few failures, blunting automated scripts.
  • Multi-factor authentication (MFA) – Require a second proof (OTP, hardware key) so a password alone is useless.
  • IP blacklisting – Block known-abusive sources, statically from threat intelligence or dynamically on threshold breach.
  • Monitoring – Track failed-login rates and alert on spikes to catch credential stuffing across many accounts.

Imagine thieves trying thousands of PINs at a National Bank of India ATM. Rate limiting lets the machine accept only a few tries per minute. Account lockout swallows the card after three wrong PINs. CAPTCHA asks a question only a human can answer. MFA requires both the card and a one-time code sent to Rajesh's phone. IP blacklisting bans a specific thief who keeps coming back. Monitoring is the security camera that calls the guard when it sees a pattern.

Understanding the Concepts

Why is defence in depth necessary?

Each control covers a gap another leaves open. An attacker must defeat all of them at once.

JAVA
package com.bank.security;

public class BruteForceDefenceInDepth {
    public static void main(String[] args) {
        System.out.println("Each layer catches a different attack:");
        System.out.println("  Rate limit: caps velocity of attempts");
        System.out.println("  Lockout: stops single-account guessing");
        System.out.println("  CAPTCHA: blocks automated scripts");
        System.out.println("  MFA: makes password alone useless");
        System.out.println("  IP blacklist: blocks persistent source");
        System.out.println("  Monitoring: catches credential stuffing across many accounts");
        System.out.println("  Short tokens: limits damage if credentials compromised");
        System.out.println();
        System.out.println("Removing any one layer opens a specific hole.");
    }
}
TEXT
Expected output:
Each layer catches a different attack:
  Rate limit: caps velocity of attempts
  Lockout: stops single-account guessing
  CAPTCHA: blocks automated scripts
  MFA: makes password alone useless
  IP blacklist: blocks persistent source
  Monitoring: catches credential stuffing across many accounts
  Short tokens: limits damage if credentials compromised

Removing any one layer opens a specific hole.

How does rate limiting work?

Cap the number of login attempts per time window per account and per IP, enforced at the API gateway with Redis.

JAVA
package com.bank.security;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;

@Service
public class LoginRateLimiter {

    private final StringRedisTemplate redis;
    private static final int MAX_ATTEMPTS = 5;
    private static final Duration WINDOW = Duration.ofMinutes(1);

    public LoginRateLimiter(StringRedisTemplate redis) {
        this.redis = redis;
    }

    public boolean isAllowed(String accountId, String ipAddress) {
        String accountKey = "login:account:" + accountId;
        String ipKey = "login:ip:" + ipAddress;

        Long accountAttempts = redis.opsForValue().increment(accountKey);
        Long ipAttempts = redis.opsForValue().increment(ipKey);

        if (accountAttempts == 1) redis.expire(accountKey, WINDOW);
        if (ipAttempts == 1) redis.expire(ipKey, WINDOW);

        boolean allowed = accountAttempts <= MAX_ATTEMPTS && ipAttempts <= 20;
        if (!allowed) {
            System.out.println("Rate limited: account=" + accountAttempts
                    + ", ip=" + ipAttempts);
        }
        return allowed;
    }
}
TEXT
Rate limit configuration:
  Per account: 5 attempts per minute
  Per IP: 20 attempts per minute (shared IPs like corporate NAT)
  Response: 429 Too Many Requests

How does account lockout work without enabling DoS?

Naive lockout lets an attacker intentionally fail logins to lock out a legitimate user. Use progressive delays instead of hard permanent locks.

JAVA
package com.bank.security;

public class ProgressiveLockout {
    public static void main(String[] args) {
        System.out.println("Progressive lockout (not hard lock):");
        System.out.println("  Failure 1-3: immediate retry allowed");
        System.out.println("  Failure 4: 30 second delay before next attempt");
        System.out.println("  Failure 5: 5 minute delay");
        System.out.println("  Failure 6+: 15 minute delay + notify account owner");
        System.out.println();
        System.out.println("WRONG: Permanent lock after 3 failures");
        System.out.println("  Attacker locks out legitimate users intentionally (DoS)");
        System.out.println();
        System.out.println("CORRECT: Progressive delay + account-owner notification");
        System.out.println("  Attacker is slowed; real user is notified and can reset");
    }
}
TEXT
Expected output:
Progressive lockout (not hard lock):
  Failure 1-3: immediate retry allowed
  Failure 4: 30 second delay before next attempt
  Failure 5: 5 minute delay
  Failure 6+: 15 minute delay + notify account owner

When do you show CAPTCHA?

After a few failed attempts, not on every login. This avoids annoying legitimate users while blocking bots.

JAVA
package com.bank.security;

public class CaptchaStrategy {
    public static void main(String[] args) {
        System.out.println("CAPTCHA trigger strategy:");
        System.out.println("  Attempt 1-2: no CAPTCHA (good UX for legitimate users)");
        System.out.println("  Attempt 3+: show CAPTCHA (blocks automated scripts)");
        System.out.println("  Successful login: reset CAPTCHA counter");
        System.out.println();
        System.out.println("Limitation: CAPTCHA solving services exist");
        System.out.println("  -> CAPTCHA is one layer, not the whole defense");
    }
}
TEXT
Expected output:
CAPTCHA trigger strategy:
  Attempt 1-2: no CAPTCHA (good UX for legitimate users)
  Attempt 3+: show CAPTCHA (blocks automated scripts)
  Successful login: reset CAPTCHA counter

Why is MFA the strongest single control?

Even if the attacker guesses the password, they need a second factor. For high-risk actions, require step-up MFA.

JAVA
package com.bank.security;

public class MfaStrength {
    public static void main(String[] args) {
        System.out.println("MFA breaks the economics of brute force:");
        System.out.println("  Without MFA: guess password -> full access");
        System.out.println("  With MFA: guess password -> still blocked by OTP/hardware key");
        System.out.println();
        System.out.println("Step-up MFA for high-risk actions:");
        System.out.println("  Login: password + OTP");
        System.out.println("  View balance: JWT only (already authenticated)");
        System.out.println("  Transfer > 100000: re-verify with OTP (step-up)");
    }
}
TEXT
Expected output:
MFA breaks the economics of brute force:
  Without MFA: guess password -> full access
  With MFA: guess password -> still blocked by OTP/hardware key

Step-up MFA for high-risk actions:
  Login: password + OTP
  View balance: JWT only (already authenticated)
  Transfer > 100000: re-verify with OTP (step-up)

How does monitoring detect attacks?

Track failed-login rate and lockout counts. Alert when they spike. Detect credential stuffing that per-account lockout misses.

JAVA
package com.bank.security;

public class AttackMonitoring {
    public static void main(String[] args) {
        System.out.println("Metrics to monitor:");
        System.out.println("  login_failure_total (counter, per service)");
        System.out.println("  login_lockout_total (counter, per service)");
        System.out.println("  login_captcha_triggered_total (counter)");
        System.out.println();
        System.out.println("Alert rules:");
        System.out.println("  login_failure_total > 1000/min -> credential stuffing attack");
        System.out.println("  login_lockout_total > 100/min -> brute force on many accounts");
        System.out.println("  Single IP with > 50 failures -> auto-blacklist IP");
    }
}
TEXT
Expected output:
Metrics to monitor:
  login_failure_total (counter, per service)
  login_lockout_total (counter, per service)
  login_captcha_triggered_total (counter)

Alert rules:
  login_failure_total > 1000/min -> credential stuffing attack
  login_lockout_total > 100/min -> brute force on many accounts
  Single IP with > 50 failures -> auto-blacklist IP

How do short-lived tokens limit damage?

Access tokens expire in minutes. A stolen credential has a tiny window of usefulness.

JAVA
package com.bank.security;

public class ShortLivedTokens {
    public static void main(String[] args) {
        System.out.println("Token strategy:");
        System.out.println("  Access token: 5 minutes (short blast radius)");
        System.out.println("  Refresh token: 24 hours (rotated on each use)");
        System.out.println("  Logout: revokes refresh token -> no new access tokens");
        System.out.println("  Current access token expires in <= 5 minutes");
        System.out.println();
        System.out.println("Refresh token rotation:");
        System.out.println("  Each use issues new refresh + access tokens");
        System.out.println("  Reuse of old refresh token -> detect theft -> revoke all");
    }
}
TEXT
Expected output:
Token strategy:
  Access token: 5 minutes (short blast radius)
  Refresh token: 24 hours (rotated on each use)
  Logout: revokes refresh token -> no new access tokens
  Current access token expires in <= 5 minutes

What should a senior engineer know?

Hash passwords with BCrypt or Argon2, return generic failure messages, apply throttling to password-reset and OTP endpoints, and combine signals into a risk score.

TEXT
Senior guidelines:
  [x] BCrypt or Argon2 for password hashing (slow by design)
  [x] Generic failure: 'Invalid credentials' (never reveal which was wrong)
  [x] Throttle password-reset and OTP endpoints (common blind spots)
  [x] Combine signals: unusual geo + new device + rising failures = step-up MFA
  [x] Progressive delays, not hard permanent locks
  [x] Apply same limits to API keys and service accounts

Visual Overview

DIAGRAM
  Brute Force Defence Layers:

  ┌──────────────────────────────────────────────┐
  │  Attacker: 10,000 credential-stuffing         │
  │  attempts per minute from botnet              │
  └──────────────────┬───────────────────────────┘
                     │
  ┌──────────────────▼───────────────────────────┐
  │  Layer 1: Rate Limit (gateway)               │
  │  Per IP: 20/min, Per account: 5/min          │
  │  -> 95% of attempts blocked with 429         │
  └──────────────────┬───────────────────────────┘
                     │ ~500 attempts pass
  ┌──────────────────▼───────────────────────────┐
  │  Layer 2: Account Lockout + CAPTCHA          │
  │  After 3 failures: CAPTCHA required          │
  │  After 5 failures: 15-min progressive delay  │
  │  -> ~50 attempts pass CAPTCHA                │
  └──────────────────┬───────────────────────────┘
                     │ ~50 attempts with correct password
  ┌──────────────────▼───────────────────────────┐
  │  Layer 3: MFA                                │
  │  Correct password but no OTP -> BLOCKED      │
  │  -> 0 successful logins without 2nd factor   │
  └──────────────────────────────────────────────┘

Key Takeaway

Defending sensitive endpoints from brute force requires layers, because each control covers a different gap. Rate limiting caps the velocity of attempts per account and per IP. Account lockout with progressive delays stops single-account guessing without enabling DoS. CAPTCHA blocks automated scripts after a few failures. Multi-factor authentication is the strongest single control because a guessed password is useless without the second factor.

IP blacklisting blocks persistent abusive sources, monitoring detects credential stuffing across many accounts that per-account lockout misses, and short-lived tokens limit the blast radius of any eventual compromise. At National Bank of India, thousands of rapid attempts hit Rajesh's account. The gateway rate-limited and dynamically blacklisted the IP, the account locked after repeated failures with progressive delays, CAPTCHA was demanded after three, and even a correct password failed without the MFA code.

The senior nuances matter: hash passwords with BCrypt so offline guessing is expensive, return generic failure messages so you never reveal whether the username or password was wrong, apply the same throttling to password-reset and OTP endpoints which are common blind spots, and combine weak signals like unusual geography, new device, and rising failures into a risk score that triggers step-up authentication.

Remember one sentence: stop brute force with layered defence: rate-limit per account and IP, progressive lockout, CAPTCHA after failures, MFA so a password alone is worthless, monitoring for failure spikes, and short-lived tokens to minimize blast radius.

---

Share
Use ← → arrow keys to navigate