CSRF & Form Security
csrf & form security csrf and form security harden html forms with synchronizer tokens, post-only mut csrf and form security
Introduction
CSRF and form security protect state-changing HTML forms from cross-site request forgery — attackers trick authenticated users' browsers into submitting unwanted POST requests. OWASP recommends synchronizer tokens, SameSite cookies, and verifying Origin/Referer headers alongside semantic HTML form methods.
Business problem
CSRF on transfer, email change, or admin action endpoints causes financial loss without XSS — victim visits attacker page while logged in; hidden form auto-submits. HTML form semantics (method, action) and token placement matter.
- OWASP A01: Broken access control includes CSRF on missing token validation.
- API + cookie auth: SPAs using cookie sessions need CSRF tokens on mutating requests.
Why this feature exists
Browsers send cookies automatically on cross-origin form POST to same site — server must verify intent via unguessable token or SameSite=Strict/Lax cookie policy. HTML forms are the original CSRF vector predating fetch API.
- GET mutations: Anti-pattern — CSRF via img src — ban state change on GET.
- SameSite: Lax blocks most cross-site POST cookies; Strict stronger; None requires Secure.
Browser internals
Form submission navigates or fetches with credentials include for same-site cookies. Hidden input csrf token must be unpredictable per session — not in cookie alone (double-submit variant excepted with care).
- autocomplete=off on token field optional — token not user password.
- multipart: CSRF applies equally to file upload forms.
Rendering workflow
Secure form HTML: method=post for mutations, action to same origin HTTPS, hidden CSRF input from server session, enctype appropriate, no sensitive ops on GET links styled as buttons.
- Double submit cookie: Token in cookie + header/body — must use __Host- prefix and Secure.
- SPA: Meta tag csrf-token + fetch X-CSRF-Token header from readable cookie pattern.
Feature deep dive
Production HTML form security pattern (OWASP CSRF Prevention Cheat Sheet):
- Token: Cryptographically random per session or request.
- Validate: Server rejects missing/mismatch before business logic.
- SameSite: Session cookie SameSite=Lax minimum.
- Origin check: Supplement token with Origin/Referer validation.
<form action="/account/email" method="post" autocomplete="on"><input type="hidden" name="_csrf" value="{{ csrfToken }}"><label for="email">New email</label><input id="email" name="email" type="email" required autocomplete="email"><button type="submit">Update email</button></form><!-- ❌ Never: <a href="/account/delete">Delete</a> without POST + token -->
Accessibility analysis
CSRF hidden fields should be aria-hidden from SR as decorative — not focusable. Use POST redirect after mutation with PRG pattern — screen reader users get clear success page.
- autocomplete tokens: Don't confuse CSRF field with user autocomplete — distinct name _csrf.
SEO impact
Minimal SEO impact — ensure CSRF-protected admin forms are noindex. Public forms (newsletter) still need bot protection (honeypot) separate from CSRF for anonymous users.
Security considerations
OWASP CSRF defenses layered: Token required; SameSite cookies; re-auth for sensitive ops (password, payment); custom header on AJAX (not simple CORS request).
- Login CSRF: Force victim into attacker's session — separate token on login form.
Performance impact
Per-request tokens prevent caching of form HTML page — acceptable for authenticated pages. Anonymous forms may use session-less double-submit with signed token.
Real production example
Express + csurf pattern (modern alternatives: custom middleware):
- SPA: Issue token in cookie + require header on API POST.
app.get("/transfer", (req, res) => {res.render("transfer", { csrfToken: req.csrfToken() });});app.post("/transfer", csrfProtection, (req, res) => {// validate token before transfer logic});
Enterprise usage
Banks add step-up MFA on wire transfer even with valid CSRF token — defense in depth. PCI scope isolates payment form HTML to hardened subdomain.
Common production failures
CSRF token omitted on JSON API when Content-Type application/json bypassed SameSite in old browsers — fixed with custom header requirement. GET /api/delete?id= CSRF classic — changed to POST.
Architecture review questions
- All mutating forms use POST with CSRF token?
- Session cookie SameSite=Lax or Strict?
- No state-changing GET links?
- Origin/Referer validated on sensitive endpoints?
- Login and password change have CSRF + re-auth?
Hands-on project
Project: Add CSRF hidden field to HTML form; implement server validation; demonstrate blocked cross-origin POST with curl without token.
Interview questions
Explain CSRF attack step by step.(Advanced)
Victim authenticated to bank.com with session cookie. Victim visits evil.com which contains hidden form POST to bank.com/transfer with attacker's account. Browser sends session cookie automatically. Server executes transfer without victim intent unless CSRF token validated.
Follow-up: SameSite=Lax enough alone?
CSRF protection for SPA with cookie auth?(Advanced)
Double-submit cookie or synchronizer token in meta + X-CSRF-Token header on fetch mutations; SameSite=Lax session cookie; verify Origin; avoid cookie auth for pure API if possible — use token auth. Require custom header so simple form can't forge.
Follow-up: JWT in localStorage CSRF risk?
Why not use GET for delete action?(Intermediate)
GET should be safe/idempotent — browsers, proxies, prefetchers, and img tags can trigger GET without user intent. CSRF via <img src='/delete?id=1'>. Use POST with token for all mutations.
Follow-up: GraphQL CSRF differences?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
CSRF and form security harden HTML forms with synchronizer tokens, POST-only mutations, SameSite cookies, and Origin validation per OWASP — essential whenever session cookies authenticate state-changing requests.