Login page security mistakes developers still make
· 6 min read
Authentication is a solved problem on paper. In practice, code reviews keep turning up the same login security mistakes: a custom reset feature bolted on late, a helpful error message a client asked for, a staging shortcut that shipped to production. None of them look dramatic in the codebase, which is exactly why they survive.
This is a checklist for developers and technical leads. Each mistake explains why it is dangerous at a conceptual level and what to do instead. No attack tooling needed, just an honest look at your own flows.
Mistake 1: Letting attackers discover which accounts exist
Username enumeration happens when the application behaves differently for registered and unregistered identifiers. The obvious version is a message like “User not found”. The subtler versions are easy to miss:
- A signup form that says “This email is already registered”.
- A reset page that confirms “Link sent to r***@gmail.com” only for known accounts.
- Response times that differ because the password hash is only computed when the user exists.
- Different HTTP status codes or redirect targets for the two cases.
Fix: return one generic message for failed logins and reset requests. For timing, run a dummy hash comparison when the user is not found so both paths do similar work. For signup, consider sending an email to the address (“someone tried to register with your email”) rather than displaying the conflict on screen. Where the business insists on showing it, at least rate-limit that endpoint heavily.
Mistake 2: Predictable or long-lived reset tokens
Password reset links are effectively temporary passwords. Common flaws include tokens built from the user ID and a timestamp, short numeric codes with no attempt limit, tokens that never expire, and tokens that stay valid after use.
Fix: generate tokens with a cryptographically secure random generator (for example, random_bytes in PHP or the secrets module in Python) with enough length that guessing is impractical. Store only a hash of the token in the database, so a leaked table does not hand out working links. Expire tokens quickly, mark them used on first redemption, and invalidate outstanding tokens when the password changes. If you use short OTP codes instead, cap attempts per code and expire the code after a few failures.
Watch the reset link’s host
If the link URL is built from the incoming request’s Host header, a manipulated header can cause emails that point to a domain the attacker controls. Build reset URLs from a configured application URL, never from request input.
Mistake 3: Any part of authentication running over HTTP
Some older sites serve the login form over HTTPS but post it to an HTTP endpoint, or serve the form page over HTTP and submit to HTTPS. Both are unsafe: in the second case, a network attacker can alter the insecure page before the user types anything.
Fix: serve the entire site over HTTPS, redirect all HTTP requests, set the Secure flag on session cookies, and add an HSTS header once the setup is stable. Check that API endpoints used by mobile apps follow the same rule.
Mistake 4: Storing passwords in a recoverable form
Plain text, reversible encryption, or fast unsalted hashes such as MD5 and SHA-1 still appear in legacy systems and quick custom builds. Occasionally passwords also leak through side channels: debug logs that dump request bodies, error trackers capturing form fields, or welcome emails that include the chosen password.
Fix: use your framework’s password hashing API with bcrypt or Argon2id. For legacy hashes, rehash transparently at the next successful login and force a reset for accounts that never return. Scrub password fields from logs and monitoring tools, and never send passwords by email or WhatsApp.
Mistake 5: No limits on repeated attempts
A login endpoint without throttling accepts unlimited guesses. Developers sometimes add limits to the web form but forget the API route, the mobile app endpoint, the XML-RPC interface in WordPress, or the OTP verification step.
Fix: apply throttling to every route that verifies a secret. Combine per-account and per-IP limits, and prefer progressive delays to permanent lockouts so attackers cannot deliberately lock out real users. Log throttling events so someone can see when they spike.
Mistake 6: Keeping the same session after login
If the session identifier issued to an anonymous visitor remains valid after they sign in, anyone who planted or learned that identifier earlier inherits the logged-in session. This is known as session fixation.
Fix: regenerate the session ID on login, on privilege changes such as entering an admin area, and on logout. Destroy server-side session data at logout, and after a password change end other sessions or offer the user a clear option to do so.
Mistake 7: Trusting the client for authentication decisions
A surprising number of apps decide what a user may see based on a value the browser can edit: a role stored in local storage, a hidden form field, or a decoded but unverified token. Another variant is a two-step login where step two can be reached directly without completing step one.
Fix: make every authorisation decision on the server using server-held session state or a signature-verified token. For multi-step flows, record completion of each step server-side and check it before continuing.
A pre-release review for any login flow
Before shipping changes to authentication, walk through these checks with a second developer:
- Failed login, unknown user and wrong password produce identical responses.
- Reset tokens are random, hashed at rest, single-use and short-lived.
- No page, form action or API involved in sign-in uses HTTP.
- Passwords use the framework’s hashing API and never appear in logs.
- Every credential-checking route has throttling, including APIs and OTP steps.
- Session ID changes at login and logout destroys the session server-side.
- Roles and step completion are checked on the server for every request.
Automated tests for these behaviours are cheap to write and stop regressions the next time someone refactors the auth code.
Getting a second pair of eyes on authentication code
Most of these flaws are invisible in normal testing because the happy path works perfectly. Schedule a focused review of login, signup, reset and session code at least whenever those files change. Our team reviews and rebuilds authentication as part of custom software development, and existing clients can raise a review through support.
Frequently asked questions
Is showing “email already registered” on signup really a problem?
It reveals which emails have accounts, which helps targeted phishing and password guessing. The risk varies by site, so if you keep the message, rate-limit the endpoint and monitor it.
How long should a password reset link stay valid?
Many applications use somewhere between 15 minutes and an hour. Shorter is safer, provided emails arrive promptly enough for users to act in time.
Should I build my own authentication or use the framework’s?
Use the framework’s maintained authentication components wherever possible. They have been reviewed widely, and custom code tends to reintroduce the mistakes on this list.
Do these mistakes apply to mobile app logins too?
Yes. Mobile apps talk to APIs that need the same generic errors, throttling, HTTPS, token handling and server-side checks as a web login form.
Thinking about a website?
See what a package covers and what it costs, or ask us about your own project.