EXERCISE 1 — A minimal synchronizer-token defence in Express ============================================================= const crypto = require("crypto"); const express = require("express"); const session = require("express-session"); const app = express(); app.use(express.urlencoded({ extended: false })); app.use(session({ secret: "change-me", resave: false, saveUninitialized: true, })); // 1. GENERATE: ensure every session has a token. app.use((req, res, next) => { if (!req.session.csrfToken) { req.session.csrfToken = crypto.randomBytes(32).toString("hex"); // CSPRNG } next(); }); // 2. EMBED: render the token as a hidden field. app.get("/transfer", (req, res) => { res.send(`
`); }); // 3+4. SUBMIT arrives here; VALIDATE with a constant-time compare. app.post("/transfer", (req, res) => { const submitted = Buffer.from(String(req.body._csrf || "")); const expected = Buffer.from(req.session.csrfToken); // timingSafeEqual throws if lengths differ, so guard that first: const ok = submitted.length === expected.length && crypto.timingSafeEqual(submitted, expected); if (!ok) return res.status(403).send("Invalid CSRF token."); res.send("Transfer processed."); }); WHY THE TOKEN MUST BE IN THE BODY/HEADER, NOT ONLY A COOKIE: - Cookies are sent AUTOMATICALLY by the browser on every request to the domain -- INCLUDING forged cross-site requests (ambient authority, Chapter 2). That's the very thing CSRF abuses. - If the token lived only in a cookie, the attacker's forged request would carry that cookie too (the browser attaches it), and the check would pass. Zero protection. - The defence depends on the token travelling somewhere the attacker CAN'T set on a cross-site request: the request BODY (hidden field) or a custom HEADER. The attacker can't read it (Same-Origin Policy) to put it there, so a forged request lacks the correct value and is rejected. - In short: the cookie proves "who"; the body/header token proves "this came from a page the server itself rendered." Only the second is unforgeable cross-site. NOTES: - timingSafeEqual prevents leaking match progress via response timing. - In production use a maintained library (csrf-csrf, or the framework's built-in) rather than hand-rolling -- this is for understanding.