How to Prevent Login Brute-Force With Rate Limiting

A login endpoint with no attempt tracking lets an attacker try millions of passwords per minute. Adding per-IP rate limiting - block after 5 failures in 60 seconds, return 429 - stops brute-force with roughly 20 lines of Python.

Security Engineerpythonflaskrate-limiting

The threat: unlimited password guesses

Without rate limiting, an attacker can POST to /login as fast as the server will accept connections. A four-digit PIN has 10,000 combinations; at 1,000 requests per second that's cracked in 10 seconds. The fix is to count failed attempts and return 429 Too Many Requests once the threshold is crossed.

Tracking attempts in memory

Use a defaultdict(list) that maps each client IP to a list of timestamps for recent failures. Before processing a login, prune expired entries and check the count:

import time
from collections import defaultdict
from flask import Flask, request, jsonify

app = Flask(__name__)

_attempts = defaultdict(list)
MAX_ATTEMPTS = 5
WINDOW = 60  # seconds

@app.post("/login")
def login():
    ip = request.remote_addr or "unknown"
    now = time.time()

    # prune timestamps outside the window
    _attempts[ip] = [t for t in _attempts[ip] if now - t < WINDOW]

    if len(_attempts[ip]) >= MAX_ATTEMPTS:
        return jsonify({"error": "Too many attempts"}), 429

    data = request.get_json()
    username = data.get("username", "")
    password = data.get("password", "")

    user = check_credentials(username, password)
    if user:
        _attempts.pop(ip, None)   # clear on success
        return jsonify({"message": "Login successful"}), 200
    else:
        _attempts[ip].append(now)  # record only on failure
        return jsonify({"error": "Invalid credentials"}), 401

Key design decisions

What a blocked response looks like

# after 5 wrong attempts:
curl -s -o /dev/null -w "%{http_code}" \
  -X POST http://localhost:8000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "wrong"}'
# -> 429

Production upgrades

In-memory tracking works per-process but resets on restart and does not span multiple instances. For production:

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How do I add brute-force protection to a Flask login endpoint?

Track failed login attempts per IP in a dict mapping the IP to a list of failure timestamps. Before each attempt, drop entries older than your window (e.g. 60s) and return 429 if the count hits your limit (e.g. 5). Clear the entry on a successful login.

What HTTP status code should a rate-limited login return?

Return 429 Too Many Requests. It is the standard code for rate limiting and lets clients, WAFs, and monitoring tools distinguish a blocked request from a wrong-password 401.

Why doesn't in-memory rate limiting work in production?

In-memory state lives in a single process - it resets on restart and is invisible to other instances behind a load balancer. Production apps use Redis (INCR + EXPIRE) so limits persist and span every server.

How do you prevent brute-force login attacks?

Layer a few defenses: rate-limit attempts per IP and per account (e.g. block after 5 failures in 60s, return 429), add exponential back-off or a temporary lockout, require CAPTCHA after repeated failures, enforce strong passwords, and enable multi-factor authentication. Rate limiting is the first and highest-impact layer.

How many failed login attempts should trigger a lockout?

A common policy is 5 failed attempts within a short window (30-60 seconds), then a temporary block rather than a permanent lock - permanent locks let an attacker lock out real users by guessing their email. Tune the threshold to your traffic.

Keep learning

Close a SQL Injection in a Search EndpointSecurity projectNeutralize a Stored XSS in CommentsSecurity projectRemove Hardcoded Credentials From Source CodeSecurity projectSecurity roadmapStep by step to hiredSecurity interview questionsSTAR answersAll Security projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →