Verifying who the user is. Get this wrong and an attacker can impersonate users, read private data, act on their behalf, and steal money.
Use an auth provider first
But even with a provider, you still need to understand how auth works: that's what this note covers.
1 · Password storage: the evolution
How to store a password correctly went through four stages:
- Plaintext
A database breach directly exposes plaintext passwords such as 12345.
- Salt
A unique random salt prevents precomputed lookup reuse. bcrypt already incorporates a salt; it is not an unsalted-hash example.
- Cost
Argon2id or bcrypt deliberately makes each guess expensive. Tune cost to your system; 400 ms is illustrative, not a universal requirement.
- Store and verify
Store the algorithm’s encoded hash with its parameters and salt. Never use plain MD5 or SHA-256 as a password-storage algorithm.
Use a modern slow password hash with a unique salt. A salt alone does not make a fast hash suitable.
How to read this model
A password inspection press with separate salt and cost controls. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Why each stage fails (and how the next fixes it)
Stage 1: plaintext. Databases get breached every day. If you store the password as-is, the attacker gets every user's real password. And ~70% of users reuse passwords across sites, so one breach cascades.
Stage 2: hash. A hash is a one-way, fixed-length output. Same input → same output. You can't reverse it. So you store hash(password).
But attackers built rainbow tables: precomputed maps of common password → hash. If your hash("12345") matches their table, they reverse it instantly.
Stage 3: salt. Add a random per-user string before hashing: hash(password + salt). Store the salt too. Now two users with the same password have different hashes, and rainbow tables return zero matches.
Stage 4: slow hash. GPUs can compute billions of SHA-256 hashes per second. So an attacker with your leaked hashes + salts can still brute force common passwords offline. The fix is a slow hash (Argon2id, bcrypt) with a cost factor you tune: e.g. 400 ms per hash. A real user won't notice 400 ms on login; an attacker goes from billions of guesses/second to a handful.
2 · Sessions (stateful auth)
Once a user logs in, you can't ask for the password on every request. You create a session:
- Authenticate
Check the supplied email and password.
- Create session
Generate an unpredictable session ID with at least 128 bits of entropy; the example allows 128–256 bits.
- Store and set cookie
Store the ID and metadata in a database or Redis. Set appropriate HttpOnly, Secure, and SameSite cookie flags.
- Look up on requests
The browser sends the cookie; the server looks up the session to establish identity. Delete or expire state to revoke it.
The cookie carries an unguessable identifier; the server owns session state and revocation.
How to read this model
An opaque-key dispenser beside a session archive. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Cookie flags you must set
| Flag | What it does |
|---|---|
| HttpOnly | JavaScript cannot read the cookie → XSS can't steal the session |
| Secure | Cookie sent only over HTTPS → not sniffable on public Wi-Fi |
| SameSite | Strict/Lax → not sent on cross-site requests → blocks CSRF |
3 · JWT (stateless auth)
A JWT is the whole session data, handed to the client. The client sends it back on every request; the server stores nothing.
- Header
The header names the algorithm, illustrated by HS256. JWT segments use base64url encoding.
- Payload
Claims such as sub, iat, and role are encoded, not encrypted.
- Signature
For HS256, compute the HMAC over header.payload with the secret. Verify the expected algorithm, signature, issuer, audience, and time claims.
A JWT payload is readable. Never put secrets in it just because it is signed.
How to read this model
A three-part message holder beside a signing die. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
The server signs header.payload with a secret key. On each request it re-verifies the signature: if the payload was modified, verification fails and the server rejects it.
JWT trade-offs (the security angle)
| Concern | Problem | Mitigation |
|---|---|---|
| Revocation is hard | Can't delete a token from the client | blacklist tokens; short expiry + refresh tokens |
| Payload is readable | base64, not encrypted: anyone can decode it | never store secrets in claims |
| Storage | localStorage is XSS-stealable | use an HttpOnly cookie |
- Login
Issue an access token and refresh token A. The original example uses a 15-minute access lifetime and a 7-day refresh lifetime.
- Access
Use the access token for API calls while it remains valid.
- Rotate
An accepted refresh consumes A and returns a replacement refresh token B plus a fresh access token.
- Reused A
Reject reuse of the consumed token and apply the system’s token-family revocation policy. Rotation needs server-side tracking.
Refresh-token reuse is a signal to revoke the affected token family and reauthenticate.
How to read this model
A single-use credential magazine with a revocation latch. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
4 · Rate limiting: mandatory on auth
Without rate limiting, an attacker can hammer your login endpoint with millions of guesses, or crash the server with raw volume. Layer it:
- Per IP
The example allows 10 attempts per minute per IP.
- Per account
The example locks after 5 failures in 15 minutes. This can catch attempts spread over rotated IPs.
- Global
A system-wide limit of N attempts per minute protects total capacity.
- Policy
Authentication endpoints need deliberate limits, recovery rules, and protection against lockout abuse.
Limits at different scopes catch different attack patterns. These numbers are example policies.
How to read this model
Three independently operated admission latches. The object is a teaching analogy. Inspecting a part highlights its role; it does not execute or simulate the software. Cabinet trays are records, workpieces are data, and latches are checks. Unless an explanation states a quantity, compartments and sample workpieces do not denote data counts. Part dimensions are not a quantitative scale.
Terms: GLOSSARY · Related: cache · communication · Next: authorization · Up: security mindset