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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions