All posts

January 25, 2026 · 6 min read

Rate Limiting Your Login Form: An Afternoon Fix That Closes an Easy Door

I've lost count of how many login forms I've tested that let you guess a password as many times as you want. No lockout, no delay, no warning email, nothing. It's one of the first things I check now on any app that got built and shipped fast, and it's almost always wide open.

Over the last year, "vibe coding" went from a phrase Andrej Karpathy used in a post on X to something a major dictionary named its word of the year. A lot more apps are getting built and launched quickly now, which is genuinely great. It also means the boring, unglamorous stuff, like putting a limit on how many times someone can hit your login form, is exactly the kind of thing that gets skipped when you're moving fast and nobody told you it mattered.

Why an open login form is such an easy target

A login form with no limit isn't a subtle bug you need a trained eye to spot. It's an open door with a sign on it. Anyone can point a script at that endpoint and run through thousands of common passwords against a real email address in an afternoon. No clever exploit chain, no deep knowledge of your codebase. Just patience, a list of leaked passwords from some other breach, and a loop.

Signup forms have the mirror problem. Nothing stops a bot from creating ten thousand accounts overnight to grab a referral bonus, hoard a promo code, or fill your user table with garbage you now have to clean up. Both directions get ignored for the same reason: they work fine in every demo, every test, every walkthrough with a real human clicking slowly. The gap only shows up when something inhuman starts clicking instead.

What rate limiting actually is

Strip the jargon out and it's one sentence: after so many attempts in so much time, slow down or stop responding. That's the whole concept. You are not building a fraud detection system or a machine learning model to spot bad actors. You're putting a speed bump on a road that right now has none.

Three numbers to decide before you write any code

  • How many attempts count as normal. Five wrong passwords in a row from one place isn't a typo streak, it's a guess.
  • How long a window you're counting over. Five to fifteen minutes is a reasonable starting point for a login form.
  • What happens once the limit is hit. A short delay, a temporary lock on that account, or a CAPTCHA on the next attempt. Pick one, ship it, tune it later.

Building it in an afternoon

You don't need a security background for this part. You need an hour with your framework's documentation. Most backend frameworks, whatever you're using, ship a rate-limiting library or middleware that takes a few lines to wire in. You pick a key to count attempts against (IP address plus the username or email being tried, not IP alone), pick somewhere to store the count, and plug in your three numbers from above.

If you're running one small server, an in-memory counter is fine to start with. If you're on serverless functions or multiple server instances, you'll want a shared store instead, because otherwise each instance counts its own attempts separately and the limit stops meaning anything. Either way, apply the same rule to your signup endpoint, your password reset endpoint, and any "resend verification email" button. Those get forgotten constantly, because people think of "login" as the only door, when a typical app usually has three or four.

Where this trips people up

  • Rate limiting only on the client side, like disabling a button in the browser after a few tries. That stops nothing. The real request still goes straight to your server if someone calls the API directly, which any script will.
  • Keying the limit off IP address alone. Office networks, VPNs, and mobile carriers put a lot of real users behind one shared IP address. Combine IP with the specific account being targeted so you don't lock out an entire building because one person forgot their password twice.
  • Setting it once and never looking again. Log what gets blocked. If the same handful of IPs trip your limit every day, that's useful information about who's actually poking at your app, not just noise.
  • Forgetting the API layer entirely. If your app has a mobile client or a public API hitting the same login logic as your website, the limit needs to live on the server behind all of them, not inside any one frontend.

None of this takes a security certification or a specialized tool. It takes deciding it matters, then spending an afternoon on it instead of a weekend.

The thing that changed how I think about this: an unprotected login form usually isn't found by someone doing careful reconnaissance on your app specifically. It's found by a script that's already running against a million other sites, and yours just happens to be next on the list that day. Whether it stops there or keeps going is decided entirely by whether that speed bump exists.

Related reading

Harbova is a security service for apps built with AI tools. Start with a free scan, and if it finds something serious, we can fix it and prove it is closed.