- security
- authentication
- web apps
5 mistakes that make your web app login vulnerable
Session tokens in localStorage, client-side only checks, passwords as the only barrier, no rate limiting, breached passwords: what to avoid and what to do instead.

In this article
The login screen is often the simplest part of an application, at least on the surface.
Email. Password. A button.
Behind those three elements sits a much larger system: authentication, sessions, authorization, defense against automated attacks and credential management. A login that works perfectly is not necessarily a secure one.
This guide covers 5 common mistakes in web app login implementations and, for each one, what to do instead.
01. Storing session tokens in localStorage
After sign-in, the application needs to keep something that lets it recognize the user on later requests. A choice you see often is storing the token directly in the browser:
localStorage.setItem("token", accessToken);It is easy to implement, but it comes at a cost.
Why it is risky
Anything in localStorage can be read by any JavaScript running on the same origin. If the application has a Cross-Site Scripting (XSS) vulnerability, the attacker's script can read the token and send it elsewhere.
XSS vulnerability
↓
Attacker's JavaScript
↓
localStorage
↓
Session token
↓
Compromised accountA session token is not UI data: it is a credential. Whoever gets it may be able to act as the user, depending on how the system is built.
What to do instead
For traditional web sessions, the most common approach is a cookie set with these attributes:
- HttpOnly: page JavaScript cannot read it.
- Secure: it is only sent over HTTPS.
- SameSite: it limits when the cookie is sent on requests coming from other sites.
HttpOnly does not remove XSS risk and does not replace CSRF protection: a malicious script can still send requests from the page while the user is signed in. It does, however, stop the credential from being read and carried away.
The rule to remember: always treat session tokens as sensitive credentials.
02. Trusting client-side checks
Take an application with an admin panel. The frontend might contain something like this:
if (user.role === "admin") {
showAdminPanel();
}That is perfectly fine for deciding what to show in the interface. It is not enough to decide what the user is allowed to do.
The frontend is not a security boundary
Everything that reaches the browser is, by definition, under the user's control. Anyone using the application can:
- modify HTTP requests;
- call an endpoint directly;
- change the frontend state;
- tamper with locally stored data;
- skip the interface entirely.
Hiding a button does not protect the API that button calls.
CLIENT SERVER
user.role === "admin" request
↓ ↓
show the button valid session?
↓
user authorized?
↓
action allowedThe real barrier is the check on the right.
Server-side authorization
Every sensitive operation must be authorized by the server. A request like:
DELETE /api/users/42must not succeed just because the frontend usually shows that command only to admins. The server has to check on its own who is making the request and what permissions they have.
The client handles the experience. The server enforces the rules.
03. Relying on the password alone
A password can be long and complex and still be compromised. It can end up in a data breach, be reused across services, be stolen through phishing or captured by malware.
That is why, when the level of risk calls for it, the password should not be the only barrier.
Multi-factor authentication
Multi-Factor Authentication (MFA) adds a second check:
Password
+
Second factor
↓
AccessEven with the password in hand, an attacker has to get past a second check. Not all factors are equal, though: an SMS code is far weaker than an authenticator app or a hardware key.
Passkeys and WebAuthn
Where the application supports them, passkeys and WebAuthn are the next step. They rely on public-key cryptography: the private key stays on the user's device and the server only stores the public one. There is no shared secret to steal from the database, and the signature is bound to the site's domain, which makes them highly resistant to phishing.
Device (private key)
↓
cryptographic signature
↓
Server (public key)
↓
verifiedChoosing between passwords, MFA and passkeys depends on the product and the risks it faces. The principle stays the same: do not design authentication on the assumption that the password will never be compromised.
04. Leaving the login without rate limiting
A public login endpoint has to accept requests from the Internet. The problem starts when it accepts them without any limit.
A person makes a few attempts. A script makes thousands.
LOGIN
LOGIN
LOGIN
LOGIN
LOGIN
...Without protection, an attacker can automate attempts: guessing passwords, testing credentials stolen elsewhere at scale (credential stuffing), or simply overloading the endpoint.
With rate limiting and throttling
The system checks how often and how requests arrive:
Requests
↓
Rate limit
↓
Within the limit → login
Over the limit → rejected or delayedWhen a limit is exceeded, the typical HTTP response is 429 Too Many Requests. Returning a 429 is not enough on its own. A solid strategy combines several signals: limits per IP address and per account, increasing delays after each failure, detection of unusual behavior.
Watch out for the opposite extreme too: an overly aggressive lockout becomes an easy way to shut out real users. Rate limiting is about regulating traffic, not bolting the door.
05. Accepting weak or breached passwords
For years, password security was summed up like this:
1 uppercase letter
1 lowercase letter
1 number
1 symbolThe result is often something like P@ssw0rd!: it follows every rule and is among the first passwords an attacker tries.
The breached password problem
Billions of credentials from past data breaches are in circulation. If a user picks a password that already appears in those lists, accepting it means accepting a credential attackers already know.
That is why current guidelines, such as NIST's, require checking new passwords against lists of breached and common passwords. The check can be done without sending the password to an outside service: Pwned Passwords, for example, only receives the first few characters of its hash.
Password
↓
Minimum length
↓
Breached password check
↓
Known or common → rejected
Otherwise → acceptedThe same guidelines advise against mandatory composition rules and forced periodic changes. What matters more is length, rejecting known passwords and allowing long passphrases and password managers.
Login security is a system
There is no single switch called "secure login". You can have MFA and handle sessions poorly. You can have excellent passwords and an endpoint with no defense against automated attacks. You can have an interface that hides every admin function and an API that never checks permissions.
Session management
↓
Server-side authorization
↓
Authentication
↓
Rate limiting
↓
Password policy
↓
Protected applicationSecurity comes from combining these layers. An authentication system is only as strong as its weakest layer.
Developer checklist
Before calling a web app login done, check at least these five points:
- Session tokens and identifiers are treated as credentials and are not needlessly exposed to JavaScript.
- Authorization is checked by the server, not only by the interface.
- A second factor or a passkey is available for the accounts and operations that need it.
- Authentication endpoints are protected against automated attempts and abuse.
- Common or breached passwords are rejected.
This list does not make an application secure on its own, but it avoids some of the most recurring mistakes.
Security starts before deployment
Security is not something to bolt on once the product is live. Decisions that look small, like where to keep a session or where to check a role, are part of the product's architecture.
A good login does more than let the user in. It decides who can get in, what they can do, how far each request can be trusted and what happens when someone tries to abuse the system. That is where login stops being a screen and becomes software engineering.
Sources
Have a project in mind?
Hash42 Labs designs and builds web apps, websites and digital products for businesses, with security built in from day one. The same principles behind this article power MANJI and Muni.
