Skip to content

[13.x] Improve origin verification in PreventRequestForgery#59198

Draft
SanderMuller wants to merge 1 commit into
laravel:13.xfrom
SanderMuller:feature/prevent-request-forgery-improvements
Draft

[13.x] Improve origin verification in PreventRequestForgery#59198
SanderMuller wants to merge 1 commit into
laravel:13.xfrom
SanderMuller:feature/prevent-request-forgery-improvements

Conversation

@SanderMuller

Copy link
Copy Markdown
Contributor

Summary

This PR makes three focused improvements to the PreventRequestForgery middleware shipped in #58400:

  1. Handle Sec-Fetch-Site: none as a safe value. The browser sets none when there is no external origin involved (e.g., forms on file:// pages, browser extensions). This is a bugfix for originOnly mode, which incorrectly throws OriginMismatchException for these requests. Go 1.25 and the OWASP Fetch Metadata policy both treat none as safe.

  2. Fall back to Origin header when Sec-Fetch-Site is absent. Clients that don't send Sec-Fetch-Site (plain HTTP contexts, older browsers, some WebViews) are forced through token validation even when the Origin header already proves the request is same-origin. This adds a lightweight Origin vs Host comparison using Symfony's getSchemeAndHttpHost(). In default mode, mismatches fall through to tokensMatch(). In originOnly mode, a matching Origin now correctly passes instead of throwing.

  3. Helpful error message on HTTP in originOnly mode. Browsers don't send Sec-Fetch-Site over insecure connections. When originOnly is enabled on plain HTTP, the exception now says "Origin verification requires a secure connection" instead of the generic "Origin mismatch", so developers know exactly what to fix.

Changes

  • hasValidOrigin(): accepts none, falls back to Origin-vs-Host, improved error on HTTP
  • originMatchesHost(): new protected method (6 lines), compares Origin header against $request->getSchemeAndHttpHost()

Precedent

  • Go 1.25 CrossOriginProtection: same three-tier strategy (Sec-Fetch-Site → Origin → allow non-browser)
  • Rails 8.2 PR #56350 / PR #56580: Sec-Fetch-Site verification + HTTP hotfix
  • OWASP Fetch Metadata policy: lists none as safe, recommends Origin fallback
  • Filippo Valsorda (words.filippo.io/csrf/): reference algorithm

@taylorotwell taylorotwell marked this pull request as draft March 16, 2026 14:18
@benbjurstrom

benbjurstrom commented Mar 19, 2026

Copy link
Copy Markdown
Member

Thanks for the PR. I like the idea of a helpful error message on HTTP in originOnly mode.

For the Origin fallback, major browsers have supported Sec-Fetch-Site for over 3 years now. If we ever completely remove the token fallback I could see adding it. Kinda on the fence here though, would be curious what the community thinks.

I do have some questions about handling Sec-Fetch-Site: none as a safe value though. Forms on file:// pages and browser extensions seem like exactly the kind of thing you'd want CSRF protection against. If they're first-party you can always exclude the middleware for that route.

Also, you reference an OWASP Fetch Metadata policy that treats none as safe. The closest thing I found was section
1.4 of the OWASP CSRF Prevention Cheat Sheet which says to allow none for "user-driven top-level navigations" but I don't think that would apply to typical form submissions. Do you have a link to the policy you're referencing?

@donnysim

Copy link
Copy Markdown
Contributor

I wouldn't take 3 years support as granted - we still have a project that needs to work on Firefox 37.

@SanderMuller

Copy link
Copy Markdown
Contributor Author

Thanks for the PR. I like the idea of a helpful error message on HTTP in originOnly mode.

For the Origin fallback, major browsers have supported Sec-Fetch-Site for over 3 years now. If we ever completely remove the token fallback I could see adding it. Kinda on the fence here though, would be curious what the community thinks.

I do have some questions about handling Sec-Fetch-Site: none as a safe value though. Forms on file:// pages and browser extensions seem like exactly the kind of thing you'd want CSRF protection against. If they're first-party you can always exclude the middleware for that route.

Also, you reference an OWASP Fetch Metadata policy that treats none as safe. The closest thing I found was section 1.4 of the OWASP CSRF Prevention Cheat Sheet which says to allow none for "user-driven top-level navigations" but I don't think that would apply to typical form submissions. Do you have a link to the policy you're referencing?

You're right that the OWASP reference is perhaps a bit more open ended than how I listed it. The OWASP section you linked is indeed what I meant, together with the example in https://web.dev/articles/fetch-metadata#step_5_reject_all_other_requests_that_are_cross-site_and_not_navigational using none as safe.

none means "no web origin initiated this." The entire purpose of CSRF protection is defending against web origins. Go 1.25, Rails 8.2, and the web.dev resource isolation policy all treat none as safe for the same reason because it's not cross-site.
The file:// scenario requires local file system access, which is outside CSRF's threat model.

On the Origin fallback: I'm open to pulling it out if the community feels it's not needed given broad Sec-Fetch-Site support. In default mode, requests without the header already fall through to token validation, so the fallback is mainly useful for originOnly edge cases.

I'm looking forward to hear how the community feels!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants