Security Deep Dive

Authentication — Proving Who You Are

Authentication answers a single question: is this request really from the person it claims to be? Get it wrong and nothing else in your security model matters. Get it right and most of your users will never notice it exists.

PasswordsHashingMFASessionsTokensOIDC
← Back to Security
Quick Facts

At a Glance

Basic Concepts

  • Authentication (AuthN): establishing identity. Different from authorization (what you can do).
  • Factors: something you know (password), have (phone, key), are (fingerprint).
  • Multi-factor (MFA): require at least two different factor types.
  • Session: a server-side record of "this browser is logged in as user X".
  • Token: a signed credential the client carries (e.g., JWT) — server verifies the signature, no DB lookup needed.
Passwords

Storing Them Without Regret

  • Never store plaintext. Never. Not even temporarily.
  • Hash with a slow, salted algorithm. In 2026 the defaults are argon2id, bcrypt (cost ≥ 12), or scrypt. Don't use MD5, SHA-1, or plain SHA-256 — they're built to be fast, which is the opposite of what you want.
  • Salt is per-password and stored alongside the hash. Modern libraries handle this for you.
  • Pepper (a server-side secret added before hashing) is optional defense in depth.
  • Re-hash on login if your cost factor or algorithm has been upgraded — users get the upgrade transparently.
  • Check against breach corpora. Use Have I Been Pwned's k-anonymity API to reject known-leaked passwords.
  • Length over complexity. NIST 800-63B recommends 8+ chars, no forced rotation, no composition rules.
// Node — argon2id
import argon2 from 'argon2';

const hash   = await argon2.hash(password, { type: argon2.argon2id });
const valid  = await argon2.verify(storedHash, password);
MFA

The Single Highest-Impact Control

FactorStrengthNotes
SMS codeWeakVulnerable to SIM swap. Better than nothing — barely.
TOTP (authenticator apps)StrongPhishable but solid baseline. Free, no hardware.
Push notificationStrongUX-friendly; "MFA fatigue" attacks are real — require number-matching.
WebAuthn / PasskeyStrongestPhishing-resistant. The future. See passkeys deep dive →
Hardware token (YubiKey)StrongestWebAuthn implementation in physical form.

If you ship one auth feature this quarter, ship MFA. MFA deep dive →

Sessions vs Tokens

Two Models, Different Tradeoffs

Server-side sessions

Login creates a server record; the browser holds a random opaque session ID in an HttpOnly Secure SameSite cookie. Every request looks up the session.

Pros: easy invalidation (delete the row), small cookie, no leaking claims to the client. Cons: requires sticky sessions or shared session store (Redis).

Default for traditional web apps. Boring in the good way.

JWT / signed tokens

Login mints a signed token containing claims (user ID, roles, expiry). Server verifies the signature on each request — no DB lookup.

Pros: stateless, easy across services. Cons: can't be revoked before expiry without an extra denylist; claims are world-readable; choosing the wrong algorithm or library has burned many teams.

Use short expiries (5–15 min) plus a refresh token. Never put sensitive data in the payload — it's signed, not encrypted. JWT deep dive →

Cookies — get the flags right
  • HttpOnly — JS can't read it (XSS mitigation).
  • Secure — only sent over HTTPS.
  • SameSite=Lax (or Strict) — CSRF mitigation. CSRF deep dive →
  • __Host- prefix — locks the cookie to its origin.
Federated Auth

Don't Build It If You Don't Have To

For most apps, the right answer is delegate. Use OIDC against Google/Microsoft/Apple, or a managed provider (Auth0, Clerk, Cognito, Authentik), and inherit decades of someone else's hard-won security work.

  • OpenID Connect (OIDC) — modern federated auth on top of OAuth 2.0.
  • OAuth 2.0 — delegated authorization; OIDC adds the auth piece.
  • SAML — older, XML-based; still common in enterprise SSO.
The Whole Lifecycle

It's More Than Login

  • Signup: verify email before granting full access; rate-limit by IP and email.
  • Login: generic error messages ("invalid credentials") — never reveal whether the email exists.
  • Brute-force protection: exponential backoff, account lockout after N attempts, CAPTCHA when suspicious.
  • Password reset: single-use, short-lived, signed token sent via email. Invalidate active sessions on reset.
  • Email change: require re-auth + send confirmation to the old address — common account-takeover vector.
  • Logout: destroy the session server-side; clear the cookie. For JWT, add to denylist or shorten TTL.
  • Session listing: let users see active sessions and revoke them.
  • Suspicious-login alerts: notify on new device / new country.
Common Pitfalls

Where Auth Goes Wrong

  • Rolling your own crypto. Use a vetted library. Always.
  • Timing-unsafe comparisons. Use constant-time compare for tokens and password hashes.
  • JWT alg: none / algorithm confusion. Pin the algorithm server-side.
  • Long-lived tokens with no revocation. Painful when an account is compromised.
  • Logging credentials or tokens. Scrub before they hit your log pipeline.
  • Leaking via account-existence oracles. "Email already in use" on signup, different timing on login — both leak who has accounts.
  • Skipping rate limits. Credential-stuffing is automated; without limits, you'll be tested daily.

See also: Broken Authentication (OWASP) →

Worked Example

The URL Shortener Login Flow

  1. User submits email + password over HTTPS.
  2. Server fetches the user row by lowercased email.
  3. argon2.verify(row.password_hash, password) — constant-time.
  4. If valid, mint a 15-minute access JWT and a 30-day refresh token (stored hashed in DB so it can be revoked).
  5. Set both as HttpOnly Secure SameSite=Lax cookies, prefixed __Host-.
  6. Increment a per-IP failed-login counter on failure; lock after 10 attempts/5 min.
  7. Audit-log: user ID, IP, user-agent, success/failure.
Continue

Related Reading