In 2026 double submit/session tokens are no longer necessary to prevent against CSRF attacks. Instead, we can use the Sec-Fetch-Site header, with a fallback of the Origin header. This has a couple of advantages:
- Removes a whole class of easy-to-make mistakes around CSRF token handling - e.g. accidentally not passing a token through to a JS request
- Removes any issues related to long-lived CSRF tokens in non-cookie session storage - e.g. a user returning to the site after their CSRF token has disappeared from the Redis backed session storage and their next actions failing
This blog post does a good job of outlining how to prevent against CSRF attacks using these headers and the issues around legacy browsers. It has been adopted in Go 1.25 in the net/http middleware.
I’ve had a go at implementing a Plug which does same: GitHub - breakroom/plug_cross_origin_protection: A Plug to protect against Cross-Site Request Forgery (CSRF) attacks using modern header-based checks instead of tokens (this is not yet in production, but likely will be after more thorough review.)
Is there any interest in adopting this approach in Plug/Phoenix and dropping CSRF tokens ( Plug.CSRFProtection — Plug v1.19.1 ) entirely? Or should I continue with a separate library?
5 Likes
Ship your thing and collect feedback! That will be useful for upstreaming it later when the time is right!
3 Likes
I was looking at the readme. I don‘t think the skip example is correct. The plug in the controller would only run after the plug in e.g. the router pipeline. So you‘d store the intent to skip after the skippable plug already executed.
I‘m also curious about the exception. Imo an exception should be the default with a Plug.Exception integration to turn it into a 403 (e.g. have a plug_status field of 403). That‘s how plug itself handles that.
I also see you using the Plug namespace. Before publishing you‘d want to change that to your own namespace: Library Guidelines — Elixir v1.12.3
3 Likes
I use Origin header alone in production. Besides what you mentioned, there is at least one more problem with CSRF tokens nowadays because:
- I store all session data server-side and only set a random key as the session cookie. (pretty common practice)
- I have forms in public accessible pages (pretty common practice)
- Bots are pounding those pages non-stop ((what can I say?)
By not generating and storing CSRF tokens, I avoided thrashing my session storage.
1 Like
Ah yes, thanks! I’ve removed that namespace, made exception the default, and removed the skip functionality, which as you highlight only works if you’re controlling the plug invocation downstream.
Yes, this is another issue that also affects us.