Authentication & Session Security
A Complete 10-Chapter Course
Table of Contents
- Foundations: AuthN vs AuthZ & the Threat Model
- Password Storage Done Right
- Login Attacks & Defences
- Sessions & Cookies
- Session Security
- Multi-Factor Authentication
- Token-Based Auth & JWTs
- OAuth 2.0 & OpenID Connect
- Account Recovery & Verification
- Testing, Pitfalls & Hardening Checklist
Foundations: AuthN vs AuthZ & the Threat Model
Across the HTTPS, CSRF, and XSS courses, one phrase kept recurring: "the session," "the credential," "riding the user's authority." This course is the foundation underneath all of them β how a system establishes who you are and keeps that fact secure over time. Chapter 1 sets the vocabulary precisely (the most-confused pair in security: authentication vs authorization) and frames the threat model the rest of the course defends against.
Authentication vs Authorization β The Core Distinction
These two are constantly conflated, abbreviated confusingly (AuthN and AuthZ), and even share the prefix "auth" β but they answer entirely different questions:
| Authentication (AuthN) | Authorization (AuthZ) | |
|---|---|---|
| Question | Who are you? | What are you allowed to do? |
| Verifies | your identity (you are who you claim) | your permissions (you may access X) |
| Happens | first β at login | after β on each protected action |
| Example | entering a correct password / passkey | being allowed into the admin panel |
| Failure looks like | "Login failed" (401 Unauthorized) | "Access denied" (403 Forbidden) |
This course focuses on authentication and the session that follows it β establishing identity and keeping that established identity secure. (Authorization β roles, permissions, access control β is a large topic in its own right; we touch it only where the two interact.)
The Three Factors of Authentication
Authentication works by checking one or more factors β categories of evidence that you are who you claim. There are three classic types:
| Factor | "Something youβ¦" | Examples |
|---|---|---|
| Knowledge | know | password, PIN, security question |
| Possession | have | phone (TOTP app), security key, passkey |
| Inherence | are | fingerprint, face, biometric |
Multi-factor authentication (MFA) means combining factors from different categories β e.g. a password (know) plus a phone code (have). Two passwords aren't MFA; they're the same factor twice. The power of MFA is that an attacker must compromise independent channels β knowing your password is useless without also having your phone (Chapter 6 goes deep).
The Wider Picture: Identity, AuthN, AuthZ, Accountability
Authentication sits in a chain of four related concepts. Keeping them distinct clarifies a lot of security design:
- Identification β claiming an identity ("I'm user
philip"). Just a claim, unverified. - Authentication β proving that claim (the password/passkey checks out).
- Authorization β granting access based on the proven identity ("philip may edit posts").
- Accountability β logging what the identity did, so actions can be traced (audit logs).
The Threat Model: What Attackers Want
Auth security is best understood by what an attacker is trying to achieve. The overarching goal is account takeover β operating as a legitimate user β and there are several routes to it:
| Attacker goal | How | Course chapter |
|---|---|---|
| Steal the credential | phishing, breach reuse, weak password storage | 2, 3 |
| Guess the credential | brute force, credential stuffing, spraying | 3 |
| Steal the session | session hijacking, XSS, fixation | 4, 5 |
| Bypass MFA | SMS interception, MFA fatigue, phishing proxies | 6 |
| Abuse tokens | JWT flaws, stolen refresh tokens, OAuth misconfig | 7, 8 |
| Hijack recovery | weak password-reset / email-verification flows | 9 |
Where This Fits With the Other Three Courses
This course completes a quartet β each protects a different part of the same login-and-session lifecycle:
| Course | Protects | Against |
|---|---|---|
| HTTPS / TLS | the credential/session in transit | eavesdropping, tampering on the wire |
| Authentication (this) | establishing & holding the identity | credential theft, session hijacking, weak MFA |
| CSRF | against abuse of an active session | forged requests riding the session |
| XSS | the page that holds the session | script stealing/riding the session |
HttpOnly, Secure, SameSite β that appeared in all three previous courses lives here, on the session cookie, and Chapter 5 finally ties it together. Account security is only as strong as the weakest link across all four.
Hands-On Exercises
Classify each as Authentication or Authorization: (a) the site rejects your password; (b) you're logged in but can't open /admin; (c) a fingerprint unlocks your phone; (d) a regular user is blocked from deleting another user's post. For each, give the likely HTTP status (401 vs 403) and justify it.
For each combination, state whether it's true multi-factor authentication and why: (a) password + SMS code; (b) password + security question; (c) fingerprint + passkey on a hardware key; (d) PIN + password. Name the factor category of each element.
π View solutionFor each prior-course attack, state which authentication threat-model goal it serves and why fixing it matters to account security: (a) XSS stealing document.cookie; (b) a CSRF forged "change email"; (c) TLS stripping on a login page. Tie each to "account takeover."
Chapter 1 Quick Reference
- Authentication (AuthN) = "who are you?" (verify identity); Authorization (AuthZ) = "what may you do?" (verify rights)
- AuthN always comes first; mnemonic: autheNtication = ideNtity, authoRization = Rights
- HTTP 401 "Unauthorized" really means un-authenticated; 403 "Forbidden" means authenticated-but-not-authorized (spec misnomer)
- Three factors: knowledge (know) Β· possession (have) Β· inherence (are); MFA = factors from different categories
- Full chain: identification β authentication β authorization β accountability
- Threat model: the goal is account takeover β via stolen/guessed credentials, stolen sessions, bypassed MFA, abused tokens, or hijacked recovery
- This course completes the quartet (HTTPS / Auth / CSRF / XSS) β they interlock; the weakest link sets account security
- Next chapter: password storage done right β hashing vs encryption, salts, and slow hashes (bcrypt/scrypt/Argon2)
Password Storage Done Right
The single highest-impact decision in authentication is how you store passwords β because databases get breached, and when one does, the difference between "annoying" and "catastrophic" is entirely down to this. The rule is simple to state and surprisingly often gotten wrong: never store passwords; store a slow, salted hash of them. This chapter explains every word of that.
Rule Zero: Never Store the Password Itself
You do not need to know a user's password β you only need to verify that someone presenting a password knows the same one. That means you never store the password (plaintext), and you never store something reversible back to the password either. When a user logs in, you apply the same one-way transform to what they typed and compare the results.
Hashing vs Encryption
| Hashing | Encryption | |
|---|---|---|
| Direction | one-way (irreversible) | two-way (reversible with key) |
| Needs a key? | no | yes β and the key can be stolen |
| Verify a password by | hashing input, comparing hashes | (would require decrypting β wrong model) |
| Right for passwords? | Yes | No |
But Not Just Any Hash: Fast Hashes Are the Wrong Tool
Reaching for SHA-256 or (worse) MD5 seems reasonable β they're one-way. The problem: those are fast hashes, designed to digest data quickly. For passwords that speed is a liability, because it lets an attacker who steals the hashes try billions of guesses per second on a GPU.
Two Attacks Fast Hashes Enable
- Brute force / dictionary attacks β try every common password (and variations) against a stolen hash. At billions/sec, weak passwords fall in seconds.
- Rainbow tables β giant precomputed tables mapping common-password hashes back to the password, so the attacker doesn't even compute β they just look up. This works because the same password always produces the same hash. Salting defeats this.
Salt: Defeating Precomputation & Identical-Password Leakage
A salt is a unique, random value generated per user and combined with the password before hashing. It's stored alongside the hash (it's not secret). Two effects, both crucial:
- Defeats rainbow tables β precomputed tables are useless because the attacker would need a separate table for every possible salt. They're forced to attack each hash individually.
- Hides identical passwords β two users with the same password get different hashes (different salts), so a breach doesn't reveal "these 500 accounts all use
password123."
bcrypt.hash(pw, 12) already produces a uniquely-salted hash, and bcrypt.compare(pw, stored) reads the salt back out of the stored string to verify. You don't store the salt separately β it's in there.
Slow Hashes: bcrypt, scrypt, Argon2
The real defence is a password hashing function deliberately engineered to be slow and resource-intensive, with a tunable cost you raise as hardware gets faster:
| Algorithm | Cost is⦠| Notes |
|---|---|---|
| bcrypt | CPU (work factor / rounds) | battle-tested, ubiquitous, safe default; ~72-byte input limit |
| scrypt | CPU + memory | memory-hard β resists GPU/ASIC better than bcrypt |
| Argon2 | CPU + memory + parallelism | modern winner (PHC 2015); Argon2id is today's recommended choice |
The cost factor is the key tunable: bcrypt's work factor of 12 means 2ΒΉΒ² internal iterations. Each +1 doubles the time. You set it so a single hash takes a noticeable fraction of a second on your server β trivial for one legitimate login, but devastating to an attacker trying billions of guesses. As hardware improves, you raise it. Memory-hardness (scrypt/Argon2) goes further: requiring lots of RAM per guess neutralizes the massive parallelism of GPUs and custom cracking hardware.
Don't Roll Your Own β and Verify in Constant Time
Use a vetted library (bcrypt, argon2 for Node; your framework's built-in). Two operational notes: the library's compare/verify function does a constant-time comparison (to avoid timing leaks), and you should plan to upgrade hashes over time β on successful login, if the stored hash used an old algorithm or low cost, re-hash the password with the current parameters and save it.
Hands-On Exercises
Explain why passwords must be hashed, not encrypted, and why even a one-way hash like SHA-256 is the wrong choice for passwords. Then implement correct storage and verification with bcrypt in Node (hash on signup, compare on login).
Explain how a salt defeats rainbow tables and hides identical passwords, using two users who both chose hunter2. Then explain why a salt alone is not enough and a slow/cost factor is still required.
A breached database is found to store passwords as unsalted MD5. Explain, step by step, how an attacker cracks the bulk of these in minutes, and write the remediation plan (algorithm choice, cost, salting, forced resets, upgrade-on-login). Compare bcrypt/scrypt/Argon2 and recommend one.
Chapter 2 Quick Reference
- Never store the password β store a slow, salted, one-way hash; verify by hashing the input and comparing
- Hash, don't encrypt β encryption is reversible (key can be stolen); hashing is one-way, no key
- Fast hashes (MD5/SHA-256) are wrong for passwords β GPUs try billions of guesses/sec
- Salt = unique random per-user value (stored, not secret) β defeats rainbow tables & hides identical passwords
- Modern password hashes generate & embed the salt automatically in their output string
- Slow hashes:
bcrypt(CPU),scrypt(CPU+memory),Argon2id(modern recommended) β with a tunable cost factor - Cost factor β set so one hash takes a noticeable fraction of a second; raise it as hardware improves; memory-hardness beats GPUs
- Pepper = optional app-side secret (not in DB) for defence in depth; don't roll your own; upgrade hashes on login
- Next chapter: login attacks & defences β brute force, credential stuffing, rate limiting, lockouts
Login Attacks & Defences
Chapter 2 made stolen hashes hard to crack. This chapter defends the live login endpoint β where attackers don't need your database, they just guess against your form. The attacks differ in how they guess, and the defences differ accordingly, but they share a theme: make guessing slow, costly, and visible.
The Family of Guessing Attacks
| Attack | How it guesses | Targets |
|---|---|---|
| Brute force | tries many passwords against one account | a single targeted user |
| Dictionary | tries common passwords / wordlists (not all combinations) | a single user, efficiently |
| Credential stuffing | replays real username+password pairs from other breaches | many accounts, reused passwords |
| Password spraying | tries a few common passwords across many accounts | many users; evades per-account lockout |
email:password pairs leaked in past breaches and replay them against your login, betting that people reuse passwords. They're not guessing β they're using known-correct credentials from elsewhere, so a meaningful percentage just work. This is why "use a unique password per site" matters, why breach-password checks (below) help, and why MFA (Chapter 6) is the real backstop: a stuffed password alone shouldn't be enough.
Defence 1: Rate Limiting
The first line of defence is limiting how many login attempts a given source can make in a window. Track attempts and reject once a threshold is exceeded:
Rate limit on multiple keys β by IP, by account, and ideally combinations β because each alone has a gap: per-account limits don't stop spraying (one try per account across thousands), and per-IP limits don't stop a botnet (one try per IP across thousands of IPs). Pair coarse IP limits with per-account limits to cover both.
Defence 2: Account Lockout β Carefully
Locking an account after N failed attempts is classic, but it's a double-edged sword:
Defence 3: Don't Leak Which Part Was Wrong (User Enumeration)
A subtle but important defence: your login (and signup, and password-reset) responses must not reveal whether a username exists. Compare:
Distinct messages let an attacker enumerate valid accounts (which then makes credential stuffing and spraying far more efficient β they only target real accounts). Beyond the message text, watch for timing leaks: if a valid username runs the slow password hash but an invalid one returns instantly, the response-time difference reveals validity. Mitigate by running a dummy hash comparison even when the user doesn't exist, so both paths take similar time. The same generic-response discipline applies to signup ("if that email is new, we've sent a link") and password reset ("if that account exists, we've emailed instructions").
Defence 4: CAPTCHA & Step-Up Friction
Introduce friction that's cheap for humans but expensive for bots β after signs of abuse rather than on every login (to avoid harming UX). A CAPTCHA (or a modern proof-of-work / risk-based challenge) triggered after a few failures, or for traffic from suspicious IP ranges, sharply raises the cost of automated guessing. Risk-based authentication goes further: challenge harder when the login looks unusual (new device, new country, impossible travel) and stay frictionless when it looks normal.
Defence 5: Breached-Password & Strength Checks
Stop bad passwords at the source. Modern guidance (NIST) favours checking new passwords against breach corpora rather than imposing arbitrary composition rules:
- Block known-breached passwords β services like Have I Been Pwned's k-anonymity API let you reject passwords that appear in breach datasets without sending the password itself. This directly defangs credential stuffing.
- Encourage length over complexity β long passphrases beat short "P@ssw0rd!"-style strings; the old "must have an uppercase, number, and symbol" rules push users toward predictable patterns.
- Don't force frequent rotation β mandatory periodic changes lead to weaker, incremental passwords (
Spring2024!βSummer2024!); rotate only on suspected compromise.
Hands-On Exercises
Distinguish brute force, credential stuffing, and password spraying β how each guesses and why per-account lockout stops one but not the others. Then explain why rate limiting must key on both IP and account.
π View solutionShow how a login form leaks valid usernames via (a) different error messages and (b) response timing. Write the fixes for both (generic message + dummy hash), and explain why the same discipline must extend to signup and password-reset responses.
π View solutionExplain why naive "lock after 5 failures" account lockout can be turned into a denial-of-service and still fails to stop spraying. Design a better defence layering (backoff, temporary locks, IP+account rate limits, breach checks, step-up CAPTCHA, MFA) and justify the ordering.
π View solutionChapter 3 Quick Reference
- Brute force / dictionary β many guesses vs one account; credential stuffing β replays real breached pairs; spraying β few common passwords vs many accounts
- Credential stuffing is the dominant real-world ATO attack β known-correct credentials, exploiting password reuse
- Rate limit on multiple keys (IP and account) β per-account misses spraying, per-IP misses botnets
- Account lockout is double-edged β naive hard locks enable DoS against your own users & miss spraying; prefer backoff + temporary locks + step-up
- Prevent user enumeration β identical generic responses on login/signup/reset, and equalize timing (dummy hash when user absent)
- CAPTCHA / risk-based step-up after signs of abuse, not on every login
- Breach checks (HIBP k-anonymity) + length over complexity + no forced rotation (NIST guidance)
- MFA outranks all of these β it makes a guessed/stuffed password insufficient (Chapter 6)
- Next chapter: sessions & cookies β how the session works after login
Sessions & Cookies
Authentication (Chapters 2β3) happens once, at login. But HTTP is stateless β every request is independent, with no memory of the last. So how does the server know that this request comes from someone who logged in five minutes ago? The answer is the session: the mechanism that carries "you are logged in" across many stateless requests. This is the thing all three previous courses kept calling "the session."
The Problem: HTTP Has No Memory
Each HTTP request stands alone. Without a session, a user would have to send their password on every single request β terrible for security (the credential constantly in flight) and UX. The session solves this: log in once, then prove "I'm still that logged-in user" cheaply on each subsequent request, without resending the password.
The Session ID: A Bearer Token for "Logged In"
After a successful login, the server creates a session and issues a session ID β a long, random, unguessable string. The client sends that ID back on every request, and the server uses it to recognize the user. The login lifecycle:
Set-Cookie: sessionId=β¦).document.cookie, or sniffing it over plain HTTP) equals account takeover without the password (XSS course Ch. 5), and why CSRF can ride it (CSRF course). The entire rest of this course's session material is about protecting this one secret string β keeping it confidential, unguessable, and short-lived. Treat the session ID with the same care as a password.
Cookies: The Usual Session Carrier
The session ID needs somewhere to live in the browser and ride along on requests. The standard home is a cookie, because cookies are sent automatically by the browser to their origin β the user (and the app code) doesn't have to attach the ID manually:
That "sent automatically" property is convenient β and is precisely the double-edged sword behind CSRF (the cookie rides even on forged cross-site requests). The cookie's security attributes β HttpOnly, Secure, SameSite β are what make it safe to carry a session ID; they get their own deep treatment in the next chapter. (Session IDs can also be carried in other ways β e.g. an Authorization header for APIs β covered with tokens in Chapter 7.)
Where the Session Data Lives: Server-Side vs Client-Side
There are two fundamentally different models for where the actual session state is stored. This is one of the most important architectural choices in auth:
| Server-side (stateful) | Client-side (stateless) | |
|---|---|---|
| Cookie holds | just an opaque session ID | the session data itself (signed/encrypted) |
| Data stored in | server memory / Redis / database | the cookie (or a token) on the client |
| Server lookup | yes β find session by ID each request | no β verify the signature & read it |
| Revocation | easy β delete the server record | hard β can't un-issue a self-contained token |
| Scaling | needs shared store across servers | no shared store needed |
Server-Side Sessions (the classic, default model)
The cookie holds only a meaningless random ID; all the real data (user ID, roles, login time) lives on the server, keyed by that ID. This is the traditional approach (PHP sessions, Express express-session, Rails) and the safest default. Its standout advantage: instant revocation β to log someone out everywhere (or kill a stolen session), you just delete the server-side record and the ID becomes worthless.
Client-Side Sessions (stateless)
The cookie (or token) contains the session data itself, protected by a cryptographic signature so the client can't tamper with it. The server stores nothing β it just verifies the signature and reads the data. This scales beautifully (no shared session store) and is the model behind JWTs (Chapter 7).
What Makes a Good Session ID
- High entropy β long and generated by a cryptographically secure RNG (β₯128 bits), so it can't be guessed or brute-forced.
- Opaque β meaningless and random; it should encode nothing (not the user ID, not a counter). A predictable or sequential ID is a catastrophe (Exercise 3).
- Unique β never reused across sessions or users.
- Generated by the framework β use your framework's session machinery (it uses a CSPRNG); never hand-roll IDs from timestamps, counters, or
Math.random().
Hands-On Exercises
Explain why HTTP being stateless makes sessions necessary, and trace the five-step session lifecycle from login to a later authenticated request. Identify precisely why the server doesn't need the password again after step 1, and what the session ID represents.
π View solutionCompare server-side and client-side sessions across storage, per-request cost, scaling, and revocation. For each of these requirements, recommend a model and justify: (a) "log out this stolen session immediately"; (b) "scale to 50 stateless API servers with no shared store."
π View solutionA site issues sequential session IDs (user-1001, user-1002, β¦) or IDs derived from Math.random()/timestamps. Explain the attack each enables and how an attacker hijacks other users' sessions. State the properties a session ID must have and why the framework's generator should be used.
Chapter 4 Quick Reference
- HTTP is stateless β the session carries "you are logged in" across requests without resending the password
- Session ID = a long random unguessable string issued at login; the client returns it each request
- The session ID is a bearer token β whoever holds it is "you"; protect it like a password (XSS theft / CSRF riding target it)
- Cookies are the usual carrier (auto-sent to origin); their
HttpOnly/Secure/SameSiteattributes secure it (next chapter) - Server-side (stateful) β cookie holds only an opaque ID; data on server; easy revocation, needs shared store
- Client-side (stateless) β cookie/token holds signed data; scales without a store; hard to revoke (JWTs, Ch. 7)
- Core trade-off: scaling (stateless) vs revocation (stateful); server-side is the simpler, safer default for most apps
- Good session ID: high-entropy (β₯128-bit CSPRNG), opaque, unique, framework-generated β never sequential/predictable
- Next chapter: session security β fixation, hijacking, rotation, timeouts, and the cookie flags in full
Session Security
The session ID is a bearer token (Chapter 4) β hold it and you're the user. This chapter is about protecting that token through its whole life: stopping it being planted (fixation), stolen (hijacking), or kept alive too long (timeouts), and configuring the cookie that carries it. This is where the HttpOnly / Secure / SameSite advice from the HTTPS, CSRF, and XSS courses finally comes home β on the session cookie.
Session Hijacking: Stealing the Token
Session hijacking means obtaining a valid session ID and using it to impersonate the user. The token can be stolen several ways β each of which a previous course addressed:
| Theft route | Defence | Course |
|---|---|---|
| Sniffing plaintext HTTP | HTTPS everywhere + HSTS + Secure cookie | HTTPS |
| XSS reading the cookie | HttpOnly cookie + fix XSS | XSS |
| Riding the session cross-site | SameSite + CSRF tokens | CSRF |
| Guessing a predictable ID | high-entropy CSPRNG IDs | Auth Ch. 4 |
Session Fixation: Planting a Known Token
A more subtle attack. Instead of stealing your session ID, the attacker gives you one they already know, then waits for you to log in with it:
session.regenerate() / equivalent β and many do it automatically on login, but verify yours does. (This is also, you may recall, the most likely cause of the "Invalid CSRF token" login error from the CSRF course: the CSRF token was tied to the pre-regeneration session.)
Session Rotation More Broadly
Regenerating the ID isn't only for login. Good practice rotates the session ID at key moments to limit the value of any single captured token:
- At login β defeats fixation (above); the most important one.
- At privilege elevation β e.g. entering an admin area or re-authenticating for a sensitive action.
- Periodically β some apps rotate the ID every N requests/minutes, so a stolen ID has a short useful life (balance against UX/complexity).
- On logout β fully destroy the server-side session, don't just clear the cookie (next section).
Timeouts: Idle vs Absolute
A session shouldn't live forever β a stolen or abandoned session is a standing risk. Two complementary timeouts:
| Timeout | Expires after⦠| Purpose |
|---|---|---|
| Idle (inactivity) | N minutes of no activity | kills abandoned sessions (e.g. walked away from a shared PC) |
| Absolute | a fixed max lifetime since login (e.g. 8β24h) | caps how long any session β even an active stolen one β can live |
Use both: idle timeout for the common "forgot to log out" case, and an absolute cap so an attacker who steals a session can't keep renewing it indefinitely by staying active. Sensitive apps (banking) use short timeouts; consumer apps balance security against the annoyance of frequent re-login. Pair timeouts with proper logout: destroy the server-side session record and clear the cookie β clearing only the cookie leaves a valid session on the server that a stolen ID could still use.
The Cookie Flags β In Full, at Last
Everything in this course's quartet converges on these attributes of the session cookie. Set all of them:
| Flag | Effect | Stops |
|---|---|---|
| HttpOnly | cookie unreadable by JavaScript | XSS stealing the session via document.cookie |
| Secure | cookie sent only over HTTPS | sniffing the cookie on plaintext connections |
| SameSite=Lax/Strict | not sent on (most) cross-site requests | CSRF riding the session |
| __Host- prefix | forces Secure, Path=/, no Domain | cookie injection from subdomains |
Secure is the HTTPS course (don't leak it on the wire), HttpOnly is the XSS course (script can't read it), SameSite is the CSRF course (don't send it cross-site), and the cookie carries the session this course is about. Four words on one Set-Cookie header encode the lessons of all four courses. Two honest caveats carried from those courses: HttpOnly stops cookie theft but not session riding (XSS can still act in-session β fix XSS), and SameSite is strong but not complete (state-changing GETs, same-site subdomains β keep CSRF tokens). Defence in depth, as always.
Hands-On Exercises
Explain session fixation step by step and why regenerating the session ID at login defeats it. Then explain the connection to the "Invalid CSRF token" login error from the CSRF course, and list the other moments a session ID should be rotated.
π View solutionFor the session cookie, write the complete Set-Cookie with all security flags, and for each flag state which theft/abuse route it blocks and which prior course it comes from. Then explain why HttpOnly alone doesn't make XSS harmless, and why SameSite alone doesn't fully replace CSRF tokens.
Distinguish idle vs absolute timeout and explain why you want both. Then explain why "logout" must destroy the server-side session rather than only clearing the cookie, and what goes wrong if you only clear the cookie while a stolen ID exists.
π View solutionChapter 5 Quick Reference
- Session hijacking = stealing a valid ID (sniffing β Secure/HTTPS; XSS β HttpOnly; cross-site β SameSite; guessing β entropy)
- Session fixation = attacker plants a known pre-login ID; defeated by regenerating the ID at login (and privilege elevation)
- Fixation regeneration is also the likely cause of the CSRF course's "Invalid CSRF token" login error (token tied to the old session)
- Rotate the ID: at login, at privilege elevation, optionally periodically, and destroy it on logout
- Idle timeout (kills abandoned sessions) + absolute timeout (caps total lifetime) β use both
- Logout = destroy the server-side session record and clear the cookie β not just the cookie
- Cookie flags: HttpOnly (XSS), Secure (HTTPS), SameSite (CSRF), __Host- prefix β the whole quartet in one header
- Caveats: HttpOnly stops theft not riding; SameSite β full CSRF defence β keep defence in depth
- Next chapter: multi-factor authentication β TOTP, passkeys/WebAuthn, and why SMS is weak
Multi-Factor Authentication
Chapter 3 ended on a promise: passwords alone are guessable, stuffable, and phishable, but MFA changes the game β a correct password is no longer enough. This chapter covers the second-factor options, from the common (TOTP apps) to the gold standard (passkeys/WebAuthn), and is honest about which are strong and which only look strong (SMS).
Why MFA Works
Recall the factor categories (Chapter 1): knowledge (know), possession (have), inherence (are). MFA requires factors from different categories, so an attacker must compromise independent channels. Your phished or stuffed password (knowledge) is useless without also defeating your possession/inherence factor. That independence is the entire value β it's why MFA is the single highest-leverage account-security control.
The Second-Factor Options
| Method | Factor | Phishing-resistant? | Strength |
|---|---|---|---|
| SMS / email OTP | possession (weak) | No | Weakest β avoid where possible |
| TOTP (authenticator app) | possession | No (code can be phished) | Good β widely available |
| Push approval | possession | No (MFA fatigue) | OK β number-matching helps |
| Hardware OTP token | possession | No | Good |
| WebAuthn / passkey | possession (+inherence) | Yes β origin-bound | Strongest |
TOTP β Time-Based One-Time Passwords
The familiar 6-digit authenticator-app code (Google Authenticator, Authy, etc.). At setup, the server and the app share a secret (shown as a QR code). Both then compute the same code from HMAC(secret, current_30-second_time_window) β so the code changes every 30 seconds and the app needs no network connection:
TOTP is a solid, ubiquitous improvement over passwords alone. Its limits: the shared secret exists on the server (a breach could expose it), and β crucially β the code can be phished. A fake login page can ask for your password and your current TOTP code and relay both to the real site in real time. TOTP raises the bar a lot, but a determined phisher can defeat it.
The SMS Problem
Push Notifications & MFA Fatigue
"Approve this login?" push prompts are convenient, but they introduced a new attack: MFA fatigue (push bombing). An attacker with your password triggers login repeatedly, spamming your phone with approval prompts until you tap "Approve" out of annoyance or confusion (this was the vector in several high-profile 2022 breaches). The mitigation is number matching β the screen shows a number you must type into the app, so you can't approve blindly β plus rate-limiting prompts and showing context (location, app).
WebAuthn / Passkeys β The Gold Standard
WebAuthn (the web standard behind passkeys) is fundamentally stronger because it's built on public-key cryptography and is phishing-resistant by design. At registration your device (phone, laptop, or a hardware key like a YubiKey) generates a key pair: the private key never leaves the device, and the site stores only the public key. At login, the site sends a challenge, the device signs it with the private key (after a local biometric/PIN unlock), and the site verifies the signature.
g00gle-login.com, your google.com passkey simply won't respond β there's no code to read out and type into the fake site, and nothing to relay. This defeats the real-time phishing proxy that beats TOTP. Combined with the private key never leaving the device and a biometric unlock (possession + inherence), passkeys eliminate entire attack classes: phishing, credential stuffing, password reuse, and server-side secret theft (the server holds only a public key, useless to a thief). This is why the industry is moving toward passkeys as a password replacement, not just a second factor.
Recovery Codes β The Backup You Must Plan For
MFA introduces a new failure mode: what if the user loses the factor (lost phone, dead hardware key)? Without a recovery path they're locked out; with a weak one, you've added a back door. The standard answer is one-time recovery codes β a set of single-use backup codes generated at MFA setup, shown once, that the user stores safely.
Hands-On Exercises
Explain why MFA defeats credential stuffing and password phishing that single-factor login cannot, in terms of "independent channels." Then explain why TOTP, despite being a real second factor, can still be defeated by a real-time phishing proxy.
π View solutionExplain why SMS is considered the weakest second factor (name at least two concrete attacks), and why "push approval" introduced MFA fatigue. Give the mitigation for push bombing and state where SMS is still acceptable, if anywhere.
π View solutionExplain why WebAuthn/passkeys are phishing-resistant where TOTP is not, focusing on origin binding and the public/private key split. Then explain why a strong MFA setup with an SMS-based recovery fallback is only as strong as the SMS, and how recovery codes should be stored.
π View solutionChapter 6 Quick Reference
- MFA requires factors from different categories β attacker must beat independent channels; a stuffed/phished password alone fails
- TOTP β 6-digit app code from
HMAC(sharedSecret, time/30); good & ubiquitous, but the code is phishable (real-time proxy) - SMS OTP β weakest: SIM-swap, SS7 interception, previews; NIST-discouraged; last resort only
- Push approval β convenient but enables MFA fatigue / push bombing; mitigate with number matching + rate limits + context
- WebAuthn / passkeys β public-key, private key never leaves device, biometric unlock; phishing-resistant (origin-bound) β the gold standard
- Passkeys can't be phished because the credential is bound to the origin β won't respond to a lookalike site; server stores only a public key
- Recovery codes β single-use backups, shown once, stored hashed; don't fall back to SMS/email and undo your strong MFA
- Recovery is the most-undermined part β secure it as rigorously as login (Chapter 9)
- Next chapter: token-based auth & JWTs β access/refresh tokens, signing pitfalls, storage, revocation
Token-Based Auth & JWTs
Chapter 4 introduced the stateless (client-side) session and promised to return to it. Here it is. Token-based authentication β most commonly with JWTs β replaces the server-side session lookup with a self-contained, signed token the client carries. It's the backbone of APIs and SPAs, and it's powerful, but it has sharp edges: signing pitfalls, storage dilemmas, and the revocation problem from Chapter 4.
What a JWT Is
A JSON Web Token is three Base64URL parts joined by dots: header.payload.signature. The header names the signing algorithm, the payload holds claims (who the user is, when it expires), and the signature proves the token was issued by the server and not tampered with:
jwt.io and see). The signature only guarantees integrity (it wasn't altered) and authenticity (the server issued it) β it does not hide anything. So never put secrets in a JWT payload (passwords, card numbers, anything sensitive). What stops tampering is that changing a claim invalidates the signature, which the attacker can't recompute without the signing key β but they can always read it.
Access Tokens & Refresh Tokens
The standard pattern splits responsibilities across two tokens to balance the stateless-vs-revocation trade-off (Chapter 4):
| Access token | Refresh token | |
|---|---|---|
| Purpose | authorize each API request | obtain a new access token when it expires |
| Lifetime | short (minutes) | long (days/weeks) |
| Sent | with every request | only to the token-refresh endpoint |
| If stolen | limited window (expires soon) | serious β but can be revoked server-side |
The logic: the access token is short-lived so a stolen one is only useful briefly (mitigating the revocation problem), while the refresh token is long-lived but tightly guarded and revocable β typically tracked server-side so it can be invalidated. This gives you most of stateless scaling with a practical revocation path.
JWT Signing Pitfalls
JWTs have a notorious history of implementation flaws. The classics:
alg: none and algorithm-confusion attacksalg: none β the JWT spec includes a "none" algorithm meaning "unsigned." A naive verifier that trusts the token's own alg header can be handed a token with {"alg":"none"} and no signature β and accept it. The attacker forges any claims they like (e.g. "role":"admin"). Fix: the server must enforce the expected algorithm, never trust the token's header to choose it. (2) Algorithm confusion (RS256 β HS256) β if the server uses RSA (public/private) but a flawed verifier lets an attacker switch alg to HS256 (symmetric), the attacker can sign a forged token using the public key (which is, well, public) as the HMAC secret. Fix: pin the exact algorithm and key type. (3) Weak HMAC secrets β a short/guessable HS256 secret can be brute-forced offline, letting the attacker mint valid tokens. Use a long, random secret. The umbrella rule: use a vetted JWT library, configure the exact algorithm explicitly, and never let the token decide how it's verified.
The Revocation Problem β and How Tokens Address It
A pure JWT is valid until it expires; you can't "delete" a self-contained token (Chapter 4). For logout, stolen-token response, or "ban this user now," you need a strategy:
- Short access-token lifetimes β the simplest mitigation; a stolen access token dies in minutes. The window, not the token, is what you control.
- Revocable refresh tokens β store refresh tokens server-side (a small amount of state) so you can revoke them; on logout/compromise, delete them and the user can't get new access tokens.
- A denylist / blocklist β keep a list of revoked token IDs (the
jticlaim) checked on each request β but this reintroduces server-side state and a lookup, partly defeating statelessness. - Token versioning β store a per-user token version; bump it to invalidate all that user's tokens at once (e.g. on password change).
Where to Store Tokens (the Cookie vs localStorage Debate)
This connects straight back to the CSRF and XSS courses β there's a genuine trade-off, not a single right answer:
| Storage | CSRF risk | XSS risk | Notes |
|---|---|---|---|
| HttpOnly cookie | yes β needs CSRF defences | token not readable by script | auto-sent; safest vs XSS theft |
| localStorage / JS memory | no β not auto-sent | readable by any XSS | must attach via Authorization header |
Storing the token in an HttpOnly cookie protects it from XSS theft but reintroduces CSRF (the cookie auto-sends) β so you add SameSite + CSRF tokens. Storing it in localStorage and sending it in an Authorization: Bearer header avoids classic CSRF but exposes the token to any XSS. As the CSRF course concluded: there's no "JWT = safe" β the risk tracks where you store it. For browser apps, an HttpOnly cookie (plus CSRF defences) is often the safer default, because XSS theft of a bearer token is full account takeover with no expiry help.
JWT vs Server-Side Sessions β When to Use Which
Don't reach for JWTs reflexively. For a single monolithic web app, classic server-side sessions (Chapter 4) are simpler, instantly revocable, and perfectly scalable with a shared store β often the better choice. JWTs shine for stateless APIs, microservices, and cross-service auth where avoiding a shared session lookup genuinely matters. Choose by your architecture, not by fashion.
Hands-On Exercises
Decode the three parts of a JWT and explain what each does. Then explain why you must never store sensitive data in the payload, and what the signature does and doesn't protect (integrity/authenticity vs confidentiality).
π View solutionExplain the alg: none and RS256βHS256 algorithm-confusion attacks, including how each forges a valid-looking token. Give the fix for both, and explain why a verifier must never trust the token's own alg header.
Explain the revocation problem for stateless JWTs and how the access/refresh split plus short lifetimes mitigate it. Then describe refresh-token rotation with reuse detection and what it catches. Finally, recommend a token-storage approach for a browser SPA and justify it against CSRF and XSS.
π View solutionChapter 7 Quick Reference
- JWT =
header.payload.signature(Base64URL); self-contained, signed, stateless - Signed, not encrypted β payload is readable by anyone; signature gives integrity + authenticity, not confidentiality; never store secrets in it
- Access token (short-lived, every request) + refresh token (long-lived, guarded, revocable) β balances scaling vs revocation
- Signing pitfalls:
alg:none(unsigned accepted), RS256βHS256 confusion (sign with the public key), weak HMAC secret β pin the exact algorithm, never trust the token'salg - Revocation problem: can't delete a self-contained token β use short TTLs, revocable refresh tokens, denylist (
jti), or token versioning - Refresh rotation + reuse detection β new refresh token each use; replay of an old one β revoke the whole family (catches theft)
- Storage: HttpOnly cookie (XSS-safe, needs CSRF defences) vs localStorage (CSRF-free, XSS-exposed) β risk tracks storage, no "JWT = safe"
- JWT vs server-side sessions: sessions simpler & instantly revocable for monoliths; JWTs for stateless APIs/microservices β choose by architecture
- Next chapter: OAuth 2.0 & OpenID Connect β delegated authorization, social login, and the flows
OAuth 2.0 & OpenID Connect
"Sign in with Google." "Allow this app to access your calendar." These are OAuth 2.0 and OpenID Connect at work β the protocols for letting one app act on your behalf at another without giving it your password. They're widely used and widely misunderstood, starting with the most important distinction: OAuth is about authorization, OIDC adds authentication.
The Problem OAuth Solves: Delegated Access Without Sharing Passwords
Suppose a photo-printing app wants to access your Google Photos. The naive (terrible) way: you give the app your Google password. Now it can do anything as you, forever, and you can't revoke it without changing your password. OAuth 2.0 solves this: it lets you grant the app limited, revocable access to specific resources, without ever sharing your credentials. The app gets an access token scoped to just what you approved.
The Four Roles
| Role | Who | Example |
|---|---|---|
| Resource Owner | you (the user) | you, who owns the photos |
| Client | the app wanting access | the photo-printing app |
| Authorization Server | issues tokens after you consent | Google's OAuth server |
| Resource Server | holds the protected data | the Google Photos API |
OAuth Is Authorization β OIDC Adds Authentication
The Authorization Code Flow (with PKCE)
The recommended flow for web and mobile apps. The key idea: the app never sees your password, and the powerful token is exchanged through a back channel, not exposed in the browser. Step by step:
state value).code_verifier, sends its hash (code_challenge) when starting the flow, and must present the original verifier when exchanging the code. So even if an attacker intercepts the authorization code, they can't exchange it without the verifier β which never left the legitimate app. PKCE is now recommended for all clients, not just public ones.
Scopes: Least Privilege for Delegated Access
A scope is a named permission the client requests (photos.read, calendar.write, email). The consent screen shows you exactly what you're granting, and the token is limited to those scopes. This is least privilege applied to delegation: request only the scopes you actually need, and users (rightly) distrust apps asking for more than they should. Over-broad scope requests are both a security risk and a conversion killer.
Common Misconfigurations
state β CSRF on the callback. The state parameter is a random value the app sends and verifies on return; without it, an attacker can splice their own authorization code into your session (a login-CSRF). Always generate, store, and check state. (2) Loose redirect_uri validation. If the authorization server allows wildcard or unregistered redirect URIs, an attacker can redirect the code/token to a site they control. Register exact redirect URIs. (3) Using the implicit flow. The old "implicit" flow returned tokens directly in the browser URL fragment β exposed and unrevocable; it's deprecated. Use authorization code + PKCE. (4) Treating an OAuth access token as proof of identity. An access token isn't an ID token β verify the OIDC ID token's signature, issuer (iss), audience (aud), and expiry before trusting its identity claims. (5) Not verifying the ID token at all β accepting it without checking the signature/issuer lets an attacker forge identities.
Social Login: What You Gain and Give Up
"Sign in with Google/Apple/GitHub" (OIDC) means users don't create yet another password, you don't store credentials, and you inherit the provider's strong authentication (their MFA, their anti-abuse). The trade-offs: you depend on the provider (outages, account bans, policy changes cascade to your users), you share some user data with them, and you must still handle account linking (same person, multiple providers) and the case where a user loses access to their provider account. For many apps it's a net win; weigh it deliberately rather than adding every provider button reflexively.
Hands-On Exercises
Explain the difference between OAuth 2.0 and OpenID Connect in terms of authorization vs authentication, and why using a raw OAuth access token as "proof of who the user is" is a security mistake. State what OIDC adds and which token carries identity.
π View solutionWalk through the authorization code flow and explain why the browser only carries a short-lived code (not the token), and why the token is exchanged server-to-server. Then explain what PKCE adds and how it defeats authorization-code interception.
π View solutionIdentify the security problem in each: (a) the app omits the state parameter; (b) the authorization server accepts any redirect_uri under the domain; (c) the app uses the implicit flow; (d) the app trusts the ID token's claims without verifying its signature/issuer/audience. Give the fix for each.
Chapter 8 Quick Reference
- OAuth 2.0 = delegated authorization β grant an app limited, revocable access to resources without sharing your password
- Four roles: Resource Owner (you), Client (app), Authorization Server (issues tokens), Resource Server (holds data)
- OAuth = authorization; OIDC = authentication β OIDC adds an ID token (JWT) with verified identity claims; use OIDC for "Sign in withβ¦"
- Authorization Code flow: browser carries a short-lived code; the token is exchanged server-to-server (back channel)
- PKCE β verifier/challenge so an intercepted code can't be exchanged; recommended for all clients
- Scopes = least-privilege permissions shown on the consent screen; request only what you need
- Misconfigs: missing
state(callback CSRF) Β· looseredirect_uriΒ· implicit flow (deprecated) Β· trusting an access token as identity Β· unverified ID token - Social login: no passwords to store + inherit provider's MFA, but you depend on the provider & share data β weigh deliberately
- Next chapter: account recovery & verification β secure password reset, email verification, the #1 takeover vector
Account Recovery & Verification
Every chapter so far has secured the front door. This chapter secures the side door β account recovery β and it's the one attackers love most, because a recovery flow is a deliberate way to gain access without the original credential. Get it wrong and all your strong passwords, MFA, and session hardening are bypassed. This is the chapter the CSRF course's "change email β password reset β takeover" chain has been pointing at all along.
The Secure Password-Reset Flow
The correct shape uses a secure, single-use, expiring token delivered out-of-band (email), never a security question or a guessable code:
/reset?token=β¦ β sent only to the registered address.Reset-Token Design β The Details That Matter
| Property | Why |
|---|---|
| High entropy (CSPRNG) | unguessable / unbrute-forceable β it's a temporary credential |
| Stored hashed | a DB leak mustn't reveal usable reset tokens (treat like a password) |
| Short expiry | limits the window a leaked link is useful (e.g. 15β60 min) |
| Single-use | invalidate on use (and on a new request) so it can't be replayed |
| Bound to one account | token maps to a specific user; can't be redirected |
Math.random() let an attacker forge a valid reset link for any account (same lesson as session IDs, Chapter 4). Use a CSPRNG. (2) Tokens that don't expire / aren't single-use β a reset link that works forever is a permanent backdoor sitting in an inbox; one that's reusable can be replayed. Expire and invalidate. (3) Host-header / link poisoning β if the reset link's domain is built from the request's Host header, an attacker can request a reset for a victim, spoof the Host so the emailed link points at the attacker's site, and capture the token when the victim clicks. Build reset URLs from a trusted, configured base URL β never from the incoming request's host.
Don't Use Security Questions
"What's your mother's maiden name?" / "First pet?" are knowledge factors (Chapter 1) that are often public or guessable β found on social media, in public records, or simply common (a handful of pet names cover most answers). They're a weak recovery method that has enabled many real takeovers. Avoid them; if a legacy system has them, treat them as one weak signal among several, never as the sole gate.
Email Verification
Verifying a new user actually controls the email they signed up with uses the same token mechanism, and matters for several reasons: it confirms the address is reachable (so recovery works), prevents signups under someone else's email, and reduces spam/abuse accounts. The flow mirrors reset: generate a high-entropy token, store it hashed with an expiry, email a single-use verification link, and mark the address verified when clicked. Keep responses generic (don't reveal "this email is already registered" to a stranger β that's enumeration, Chapter 3).
Re-Verification & Notifications for Sensitive Changes
Recovery security extends to changing the recovery channels themselves β the exact step the CSRF takeover chain exploited:
- Re-authenticate for sensitive changes β require the current password (or an MFA challenge) before changing email, password, or MFA settings. A forged or session-riding request then can't succeed (CSRF/XSS courses).
- Notify on every security change β email the user (at the old address too, for an email change) when the password, email, or MFA is changed, so an unauthorized change is at least visible and reversible.
- Invalidate sessions on password reset β after a reset, kill all existing sessions, so an attacker who had a session is logged out by the legitimate recovery.
- Cool-down / delayed change for email β some systems delay or allow a grace-period reversal of an email change, blunting silent takeover.
Hands-On Exercises
Explain why account recovery is effectively an authentication bypass and must be as strong as login. Then design a secure password-reset flow and list the five required token properties (entropy, hashing, expiry, single-use, account-binding) with the reason for each.
π View solutionIdentify and fix the bug in each reset implementation: (a) the token is the user's ID times a timestamp; (b) the reset link never expires and works repeatedly; (c) the reset URL's domain comes from the request Host header. Explain the attack each enables.
Walk through the CSRF course's "change email β password reset β takeover" chain, then list the recovery-side defences from this chapter that break it at each step (re-authentication, old-address notification, generic responses + rate limiting, session invalidation). Explain why each step is closed.
π View solutionChapter 9 Quick Reference
- Recovery is an authentication bypass by design β attackers hit the weakest path, so secure it as strongly as login (the #1 ATO vector)
- Reset flow: generic response β CSPRNG token (store hashed, short expiry) β emailed single-use link β set new password β invalidate token + sessions + notify
- Token properties: high-entropy Β· hashed at rest Β· short expiry Β· single-use Β· bound to one account
- Classic bugs: predictable tokens Β· non-expiring/reusable tokens Β· Host-header link poisoning (build URLs from a trusted base, not the request host)
- Avoid security questions β public/guessable knowledge factors
- Email verification uses the same token mechanism; keep responses generic (anti-enumeration)
- Sensitive changes: re-authenticate, notify (old address too on email change), invalidate sessions on reset, optional change cool-down
- This closes the CSRF takeover chain β recovery is where the prior courses' attacks converge; it IS auth, not an afterthought
- Next chapter: testing, pitfalls & a hardening checklist β the course finale
Testing, Pitfalls & Hardening Checklist
The finale turns the course into practice: how to test an authentication system, the broken-auth patterns to recognize, and a single deployable checklist that pulls all ten chapters β and the whole HTTPS/CSRF/XSS quartet β together.
How to Test Authentication
Auth testing is mostly about probing each part of the lifecycle for the weaknesses each chapter named:
- Password storage β confirm hashes are slow + salted (bcrypt/argon2), not MD5/SHA; that you never store or log plaintext.
- Login β try brute force / stuffing against rate limits; check for user enumeration via messages and timing; confirm generic errors.
- Session β verify the ID is high-entropy & regenerated at login (fixation); check cookie flags; test idle/absolute timeout and that logout truly invalidates server-side.
- MFA β confirm it can't be skipped by manipulating the flow; test recovery-code handling; check for MFA-fatigue protections.
- Tokens β try
alg:noneand algorithm swaps; tamper with claims; check token expiry/revocation actually works. - Recovery β test token entropy/expiry/single-use, Host-header poisoning, and that sensitive changes re-authenticate.
The Broken-Auth Catalogue
| Broken practice | Why it fails | Chapter |
|---|---|---|
| Plaintext / fast-hash / encrypted passwords | breach = mass plaintext recovery | 2 |
| No rate limiting / naive lockout | brute force / stuffing; self-DoS | 3 |
| User enumeration (message or timing) | lets attackers target real accounts | 3 |
| Predictable session IDs | session hijacking by guessing | 4 |
| No session regeneration at login | session fixation | 5 |
| Missing cookie flags / cookie-only logout | theft, riding, stale sessions | 5 |
| SMS-only MFA / weak recovery fallback | SIM-swap; downgrades strong MFA | 6 |
alg:none / unverified JWT | forged tokens, identity spoofing | 7, 8 |
OAuth without state / loose redirect_uri | callback CSRF, code theft | 8 |
| Weak password reset (predictable/eternal/poisonable) | account takeover via recovery | 9 |
The Hardening Checklist
HttpOnly + Secure + SameSite (+ __Host-) on the session cookienone); short access + revocable/rotating refresh; verify ID-token sig/iss/audstate; exact redirect_uri; use OIDC ID token for identityHow the Course (and Quartet) Fits Together
The arc: establish identity (Ch. 1 foundations, Ch. 2 password storage, Ch. 3 login defences), carry it (Ch. 4 sessions, Ch. 5 session security), strengthen it (Ch. 6 MFA), scale it (Ch. 7 tokens, Ch. 8 OAuth/OIDC), recover it safely (Ch. 9), and verify it all (Ch. 10). And it completes the quartet: HTTPS protects the secret in transit, Authentication establishes and holds it, CSRF stops it being ridden, XSS stops it being stolen. Four courses, one secret, defended end to end.
Hands-On Exercises
Write an authentication test plan covering password storage, login, session, MFA, tokens, and recovery β naming the specific weakness to probe in each area and how you'd test it (e.g. timing-based enumeration, session-ID regeneration at login, alg:none).
Audit this app against the broken-auth catalogue: it stores SHA-256 passwords, has no login rate limit, keeps the same session ID after login, stores JWTs in localStorage, and emails non-expiring reset links built from the Host header. List every flaw, its chapter, and the fix.
Produce a prioritized hardening plan for a new web app's auth (cookie sessions, password + optional MFA, email password reset). Order the measures, justify the ordering, and show how the four security courses (HTTPS / Auth / CSRF / XSS) each contribute to protecting the session.
π View solutionChapter 10 Quick Reference
- Test each lifecycle stage for its named weakness; auth bugs are logic bugs scanners miss β walk flows manually against OWASP cheat sheets / ASVS
- Broken-auth catalogue: weak hashing Β· no rate limit Β· enumeration Β· predictable/un-regenerated session IDs Β· missing cookie flags Β· SMS-only MFA Β·
alg:noneΒ· OAuth without state Β· weak reset - Checklist: slow salted hashes Β· IP+account rate limits Β· MFA (TOTP/passkeys) Β· regenerate session at login Β· HttpOnly/Secure/SameSite Β· pin JWT alg + verify ID token Β· auth-code+PKCE Β· secure reset Β· HTTPS+HSTS Β· don't roll your own
- The one principle: protect a single secret (credential β session) across its whole life β stored, proven, in transit, guessed, ridden, stolen, recovered
- Account security = the weakest link across all of it; each defence assumes the others hold
- Completes the quartet: HTTPS (in transit) Β· Auth (establish/hold) Β· CSRF (not ridden) Β· XSS (not stolen)
β Authentication & Session Security Complete β 10 / 10 chapters
From AuthN-vs-AuthZ foundations through password hashing, login defences, sessions and their security, MFA, tokens and JWTs, OAuth/OIDC, secure recovery, and a deployable hardening checklist. Paired with HTTPS, CSRF, and XSS, you now hold the complete quartet of web account security β four courses defending one secret end to end: stored irreversibly, proven without resending, kept confidential, and never guessed, ridden, stolen, or bypassed.