Basic User Authentication Strategies
IntroductionDev EnvironmentClientTransportServerAppDataMore

Basic User Authentication Strategies

March 19, 2025

Every day, billions of login attempts happen that weren't initiated by real users. Bots test email and password combinations stolen from other services, probe password reset forms for weaknesses, try to predict session tokens. Authentication is simultaneously your application's first line of defense and attackers' favorite target — because if they break it, they don't need to find any other vulnerabilities.

The Threat — What Happens When Authentication Fails

The attacker's basic goal is to get into the system as someone else — either as a specific user (like an admin), or as anyone at all, then escalate from there.

The most common scenarios:

Credential stuffing — the attacker takes a database of logins and passwords stolen from another service (there are plenty of these floating around) and tests them en masse against your application. It works because most users reuse passwords.

Brute force — systematically testing passwords with no sophisticated tactic. Effective if the application doesn't limit the number of attempts and the user picked a weak password.

Session hijacking — capturing a session identifier or authorization token. If the token is predictable, stored in the wrong place, or transmitted over HTTP, an attacker can steal it and act as the victim without ever knowing their password.

Broken password reset — password reset flows are their own class of bugs. Reset tokens that are valid too long, not tied to a specific email, or reusable after single use — each of these lets an attacker take over an account.

The Consequences

A compromised user account means: access to their data, the ability to act on their behalf, escalation if they have elevated permissions. A compromised admin account means potentially full control over the entire application and database.

For the business: liability for personal data breach (GDPR), loss of user trust, incident response costs.


Use Your Framework's Built-In Auth

Most modern frameworks have built-in authentication systems — battle-tested, patched, and maintained by thousands of developers. This is not the place to get creative.

A few examples: Django Authentication, Devise (Rails), Laravel Sanctum/Fortify, Passport.js (Express), Spring Security. Each of these handles complex security requirements — session management, CSRF protection, password hashing — and receives security patches before most developers even hear about a vulnerability.

If you're tempted to write your own authentication system — seriously, don't. Secure authentication requires correctly implementing password hashing, timing attack protection, session management, CSRF protection, brute force prevention, password reset flow, and email verification. Every one of those has subtle pitfalls; a mistake in any of them can be enough.


Social Login — Fewer Passwords, Less Responsibility

Integrating with Google, GitHub, Apple, or other OAuth providers shifts part of the authentication security responsibility to an external service — and that's not a bad thing. Large providers invest in security at a level that's hard to match in a small application.

Benefits: users don't have to create another password, providers often enforce 2FA, you have less sensitive data to store on your side.

Things to think about: your application becomes dependent on an external service; decide how to handle account linking (when a user signs up via a social account with an existing account's email); always have a fallback login method.

Important: don't implement the OAuth flow manually from scratch. Use your framework's official integrations or well-maintained libraries. OAuth is a protocol with many details where implementation mistakes lead directly to vulnerabilities.


Password Storage — If You Must

If you decide to go with your own passwords instead of social login:

Do: use algorithms specifically designed for password hashing — bcrypt, Argon2, or PBKDF2. Make sure every password has a unique salt. Set the work factor (cost) parameter high enough — it should slow down hashing to ~100–300ms.

Don't: don't store passwords in plaintext (sounds obvious, but it happens regularly). Don't use MD5 or SHA-1 — they're too fast, which makes them useless for passwords. Don't encrypt passwords instead of hashing them — encryption is reversible, hashing isn't.

Most built-in auth modules handle this automatically. Use them according to the documentation and you won't need to implement cryptography yourself.


Tokens and Sessions

For authenticating API requests, you don't make the user send their login and password on every call. Instead, you issue a token or session identifier after a successful login:

  • Session tokens — the server stores the session, the client gets an opaque identifier in a cookie (HttpOnly, Secure)
  • JWT — self-contained tokens validatable without hitting the database; convenient, but require a well-thought-out revocation strategy
  • OAuth 2.0 tokens — for integrating with external services and exposing an API

Regardless of the choice: tokens need a limited lifespan, a refresh mechanism, and the ability to be revoked. Where to store tokens on the client side (localStorage vs. HttpOnly cookie) is a separate topic — the short answer: HttpOnly cookie is safer for user sessions.


Email Verification

Email verification confirms that the user has access to the provided address. The flow:

1. Registration → generate a unique token, store in DB with expiry date
2. Send email with a link containing the token
3. User clicks → validate token (does it exist? has it expired?) → mark account as verified
4. Invalidate the token after use

The token should be random (e.g. 32 bytes from a CSPRNG), single-use, and expire after a reasonable time (e.g. 24h). Password reset links work on the same principle — just with a shorter expiry (1h is a good starting point).


Two-Factor Authentication (2FA)

2FA requires the user to provide something they know (password) and something they have (phone, hardware key). Even if the password is stolen, an attacker without the second factor can't do anything.

For applications with sensitive data — financial, medical, any where losing an account is a serious problem — 2FA is strongly recommended as a minimum. TOTP (Time-based One-Time Password, the standard used by Google Authenticator, Authy) is a straightforward and well-supported solution.

When implementing 2FA, don't forget recovery codes — single-use backup codes in case the device is lost. Without them, users can permanently lose access to their accounts.


Why This Works

Each of these techniques attacks a different link in the account takeover chain:

Framework auth — eliminates an entire class of implementation bugs. Timing attacks, incorrect hashing, session management errors — these problems have already been solved; you benefit from the work.

Password hashing (bcrypt/Argon2) — even if the database leaks, the attacker doesn't get passwords. They get hashes that require enormous computational resources to reverse. Salt eliminates rainbow table attacks. A high work factor means testing each password costs time — credential stuffing becomes impractical.

Tokens instead of passwords — every request carries a token, not a password. The token is limited in time and scope; a stolen token is useless after it expires. A password has infinite validity; a stolen password works everywhere it was reused.

2FA — adds an independent verification channel. Compromising the password is no longer sufficient to take over the account. An attacker would need to simultaneously compromise the password and have physical access to the device — that's a radically different class of attack.


Summary

Authentication isn't a place for experimentation. A few principles worth sticking to:

Technique Protects against
Framework auth (don't roll your own) Implementation bugs across all auth logic
bcrypt / Argon2 password hashing Database breach, rainbow tables, credential stuffing
Token expiry + revocation Stolen tokens remaining valid indefinitely
One-time email verification tokens Account creation with uncontrolled emails
2FA (TOTP) Account takeover even after password compromise
HttpOnly cookies for session tokens XSS stealing tokens from localStorage

Authentication answers the question "who are you?" — but that's only half the story. The question "what are you allowed to do?" is the domain of authorization, explained in The Difference Between Authentication and Authorization. The role-based permission model that makes authorization manageable is covered in Role-Based Access Control (RBAC) for Beginners. For specifics on token types and API keys, see Authentication Tokens and API Keys.


Sources: OWASP — Authentication Failures, OWASP — Authentication Cheat Sheet