engineeringdept

engineeringdept

Proposal to migrate to tokenless/header only CSRF in Plug/Phoenix

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:

  1. 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
  2. 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 · GitHub (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.20.2 ) entirely? Or should I continue with a separate library?

Most Liked

josevalim

josevalim

Creator of Elixir

Ship your thing and collect feedback! That will be useful for upstreaming it later when the time is right!

LostKobrakai

LostKobrakai

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

derek-zhou

derek-zhou

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.

Where Next?

Popular in Proposals: Ideas Top

superchris
Hello! After seeing a couple of posts from Chris and Jose about supporting web components in LiveView, I thought it would be great to ini...
New
zachdaniel
Something we do in various Ash packages is provide routes that the user forwards to in their own application. This is because we are deri...
New
shahryarjb
When proposing or suggesting something please consider: As my experience implementing getBoundingClientRect as Phoenix.LiveView.JS funct...
New
nocksock
LiveView has spoiled me and now I would LOVE to be able to write controllers like this: defmodule DeadViewWeb.ItemController do use De...
New
New
nunobernardes99
On 1.8+, when we generate an authentication system with mix phx.gen.auth we can make use of magic link login which is amazing and a great...
New
dogweather
Hi Phoenix community! First off, huge thanks for an amazing framework - Phoenix has been a joy to work with. I have a small UX suggestio...
New
Matsa59
Lets start by a story, I work with a team on a pretty big CMS with lots of modules. We split the cms using umbrella apps. So we have comp...
New
spicychickensauce
I’ve previously explored what is possible today with hacks to implement view transitions in our apps: I have since created a fork to im...
New
MUSTDOS
Hello all! assign(socket, :name, “Elixir”) Why can’t we have assign/stream take a group of atoms and maps/structs as a default to r...
New

Other popular topics Top

josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31194 112
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42158 114
New

We're in Beta

About us Mission Statement