Crux Read real auth/web-app snippets spanning the track — JWT validation, object-level authz, cookie/CSRF config, password storage — and pick the highest-leverage fix a security-minded senior makes first.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Security bugs are read in code and config, not in slogans. Each snippet below is a small piece of a real secure-app stack. Read it, find the seam, and pick the fix a senior makes before anything else — the same loop you run in a code review or an incident.
Goal
Practise the review loop across the whole track: spot the broken token check, the missing ownership check, the cookie that reopens CSRF, and the secret that turns a config into a breach — then choose the highest-leverage fix.
Snippet 1 — verifying the token
// Express middleware guarding the APIfunction authenticate(req, res, next) { const token = req.headers.authorization?.split(" ")[1]; const claims = jwt.decode(token); // decode only if (claims?.sub) { req.user = claims; // trust the payload return next(); } return res.status(401).end();}
Quiz
Completed
This middleware authenticates every request. What is the critical flaw, and the fix?
Heads-up The header parsing is fine. The fatal bug is decoding without verifying the signature — a cookie wouldn't fix forged, unsigned claims.
Heads-up Optional chaining already guards a missing header. Robustness aside, the security defect is trusting decoded claims with no signature verification at all.
Heads-up TLS protects transit; it does nothing about a forged payload. The attacker writes their own token — only signature verification with a pinned alg stops that.
The token is validated and the scope is enforced. A user with a valid token still reads another account's statements. Why, and what is the fix?
Heads-up The query uses a parameter ($1), so there's no injection. The flaw is the missing object-level authorization: a valid token and scope don't prove ownership of this id.
Heads-up Read scope is correct for a GET. Scope is coarse permission, not per-object ownership. The bug is no owns(subject, object) check on the specific account.
Heads-up Scope authorizes the action class (may read statements), not the specific record. Without an ownership check this is IDOR, OWASP's A01.
Snippet 3 — the session cookie
res.cookie("session", token, { httpOnly: true, secure: true, // sameSite not set});// elsewhere, a "convenience" routeapp.get("/account/delete", deleteAccountHandler); // state change on GET
Quiz
Completed
HttpOnly and Secure are set, so XSS can't read the cookie. What CSRF exposure remains, and how do you close it?
Heads-up HttpOnly stops JS reads, not the browser auto-attaching the cookie cross-site. The state-changing GET is a textbook CSRF target; HttpOnly is irrelevant to it.
Heads-up Encrypting the cookie value doesn't stop the browser sending it on a forged cross-site request. The fix is SameSite plus a CSRF token and moving the action off GET.
Heads-up Dropping Secure is a regression that allows the cookie over plaintext. It does nothing for CSRF and weakens transport security.
Two track-spanning failures sit in these few lines. Name both and the fix for each.
Heads-up A private repo still leaks via clones, CI logs, and accidental public exposure, and history keeps the value after deletion. A committed secret is already compromised — rotate it.
Heads-up SHA-256 is cryptographic but fast (~22 billion/s on a GPU) and unsalted here — exactly wrong for passwords. You need a slow, salted, memory-hard KDF.
Heads-up A salt defeats rainbow tables but leaves SHA-256's GPU speed intact, so common passwords still crack fast offline. The fix is a deliberately slow KDF, not just a salt.
Recap
Every snippet was a seam between two units of the track. jwt.decode authenticates nothing — verify the signature and pin the algorithm. A valid token and scope are not ownership — add an object-level owns(subject, object) check to kill IDOR. HttpOnly stops XSS theft but not CSRF — set SameSite, add a token, and keep state changes off GET. And a committed secret is already breached (rotate, don’t delete) while a fast unsalted hash hands over the password column (use Argon2id with a per-user salt). Read the code, find the seam between correct-looking layers, and fix the structural gap.