Skip to content

Add Origin / Sec-Fetch-Site verification to SessionCSRF as a cookie-independent CSRF fallback #587

Description

@adrianbj

Summary

Add an optional Origin / Sec-Fetch-Site same-origin check to SessionCSRF, as a complement to the existing session-bound token, so a legitimate same-origin form POST still validates when the session cookie isn't present on the request.

Background

SessionCSRF is a session-bound synchronizer token: the token is stored in the session and validate() / hasValidToken() compare the submitted value against it. That's solid, but it has a hard dependency — the session cookie must reach the server on the POST. When it doesn't, the token check fails and (with a friendly retry handler) the user can end up in a loop.

On real mobile/desktop traffic the cookie is absent on the POST more often than you'd expect, for legitimate same-origin submissions:

  • Cross-site entry funnels. Users arriving from an external link (email/SMS, an emailed coupon URL), returning from a payment provider (e.g. Paddle), or from an OAuth provider (Google) navigate "cross-site," so the browser withholds a SameSite=Lax session cookie on the resulting form POST. Modern Chrome evaluates the full redirect chain, so this is browser-agnostic — we reproduced it on desktop Chrome, not just Safari/iOS.
  • Session not found for the cookie's id (GC, a fresh session minted under concurrent requests, etc.) — cookie sent, but the token isn't in that session.

In all these cases the token check fails for a legitimate same-origin submission, and there's no clean recovery.

What other frameworks do

A header-based same-origin check is the modern complement and is now mainstream:

  • Laravel 13's default CSRF middleware checks Sec-Fetch-Site first and accepts same-origin requests with no token, falling back to the token otherwise (plus an originOnly mode).
  • Django supplements its CSRF token with an Origin check (and strict Referer on HTTPS when Origin is absent).
  • OWASP lists Origin/Referer verification as a primary CSRF mechanism, with SameSite as defense-in-depth only.

Origin and Sec-Fetch-Site are browser-controlled and cannot be forged cross-site, so this blocks cross-site forgery without depending on the session cookie surviving the round trip.

Request

Add an opt-in (or default) Origin / Sec-Fetch-Site verification to SessionCSRF: when a request is provably same-origin (Origin host ∈ $config->httpHosts, or Sec-Fetch-Site: same-origin), accept it even if the session-bound token check fails. Keep the session token as the primary defense and use this purely as the cookie-independent fallback.

Why this is awkward to add in userland today

SessionCSRF::validate() and hasValidToken() aren't hookable (no ___ prefix), and hasValidToken() doesn't honor $config->protectCSRF. To inject an Origin check from a site/module today you have to either seed the session token or toggle $config->protectCSRF per request — both hacky. Native support would be much cleaner and would fix a real class of mobile login/signup failures.

Happy to sketch a PR if useful. (ProcessWire 3.x / dev.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions