You should still use CSRF tokens
On May 17, 2025 by Sosthène Guédon
Listen to this article
You may think that thanks to cookies being set to SameSite=Lax
by default, CSRF (Cross-Site Request Forgery) is mostly a solved problem, but It's not and CSRF tokens are still good practice to implement.
The CSRF vulnerability
Let's first explain what a CSRF is and how to prevent them from being an issue.
What it is
Here's a quick example of the vulnerability:
Imagine you have a website, https://example.com. In that website, if you are logged-in as an admin, you have access to a form that allows you to give someone admin powers:
User:
Submit
This gives you a field where you can input a username, and then click the button to submit it. Obviously, this kind of admin functionality is only accessible to users logged-in as admin.
When clicking the button, your browser navigates to the page https://example.com/set-admin?user=<value>
where <value>
is the value the of the user ID that you inputted into the user
field. The server then checks that you are an admin, and adds the user you told it to to the list of admins of the website.
One could think that as long as the server correctly checks that you are an admin there is no risk, but that's not true.
A CSRF attack here would be simple. As an attacker controlling some other website, say https://evil.example
, could embed just the exact same form
on their website, changing only the labels, and try to trick you into submitting it with a username you didn't intend.
Even if the form is on another website, submitting it will still work. Even worse, the attacker could make use of Javascript to automatically submit the form when the page is loaded. Now they only have to trick you into opening their site once, and they can make anyone admin on https://example.com
.
How to prevent it
The usual solution to prevent such attacks is to use CSRF tokens. Essentially, the idea is to embedded in the form as an hidden element a "token", which is random and unique for each user and each session, and have the server check that the token is properly included in the form, and corresponds well to the user. Since this token is secret and per-user, the attacker cannot guess it to include it in their form, and thus cannot create a fake form on their site that would fool the server. This method is the one that should be used everywhere, ideally using facilities provided by whatever framework you are using.
There are other prevention mechanisms. All modern browser today will by default set cookies to have the SameSite=Lax
attribute.
This attribute means that form submissions (and JS created-requests) originating from other websites will not include cookies.
This pretty much "solves" CSRF because almost all CSRF vulnerabilities require cookies to check for some authentication.
But it should be noted that CSRF can still be attained.
Some cookies need to be set with SameSite=None
for compatibility. If you do that this protection is void.
If, as in the example I gave, the form doesn't use POST
submission, SameSite=Lax
does not save you, because navigating to the page still sends the cookies. This is one of the reasons why you should use POST
for endpoints that can change data.
We will see an example from personal experience, why SameSite=Lax
or SameSite=Strict
are still not enough to protect websites in many cases, and why CSRF tokens are still a good practice.
A subtle case
When I first learned about this class of vulnerability, I was a student and I worked on a student project that was hosted for all students of the school.
The frontend of the project was built in client-side React
and used GraphQL
through Apollo
for both the front-end and the backend.
Apollo protects from CSRF by leveraging the CORS mechanism, which worked to prevent CSRF for the GraphQL
API.
However there was one part of the backend that was using standard forms, the "adminview", available to sysadmins.
I initially thought that SameSite=Lax
was already protecting these forms.
A couple of months ago however, I learned that the definition of SameSite
is not the same concept as cross-origin
requests.
Indeed, while the browser will consider a.example.com
and b.example.com
to be cross-origin, it will consider them to be same-site!
In our case, this was a problem.
In general, you own the domain and everything that goes next to it, but in our case, many students websites were subdomains of a single domain. So the project I worked on was hosted at project.example.com
, and many students had access to the domains other-project.example.com
.
This meant that they could very well have used their websites to perform a CSRF attack on me and the other admins of the projects.
In that case, the usual protections were not enough and CSRF tokens were required to solve this issue for good!
What I gather from this
I draw multiple conclusions from this:
- Always use CSRF tokens: You don't know what subtle configuration could be causing CSRF vulnerabilities.
- Double protect sensitive endpoints: For particularly sensitive endpoints (such as admin panels), consider having multiple layers of security.
SameSite=Lax
or strict, checking theSec-Fetch-Site
header. For sensitive endpoints, it might make sense to exclude some browsers that may not include ways to perform multiple-checks.