The most common way of preventing cross-site request forgery attacks is to use an anti-CSRF token, which is a unique value set and then verified by a web app. CSRF is a client-side attack that can be used to perform unintended actions within a user session, including redirecting to a malicious website or stealing session data. Correctly generating and using CSRF tokens is crucial to protect users against CSRF attacks and their consequences.
The idea behind anti-CSRF tokens (also called just CSRF tokens) is simple: to give the user’s browser a piece of information (a token) that it then has to send back to prove a request is legitimate. To be effective, the token must be unique and impossible to guess for a third party. The application must also verify the token and only process HTTP requests after successful verification to ensure that only the legitimate user can send requests within their authenticated session.
Cross-site request forgery is a web vulnerability that allows an attacker to send requests impersonating a legitimate user’s browser activity to perform state-changing actions on a server. A typical CSRF attack involves tricking a user into opening a malicious link that then attempts to execute unintended actions within a user’s active application session. The impact of CSRF depends on the application being targeted.
Learn more about cross-site request forgery.
As so often in security, there are many ways to implement anti-CSRF tokens and many details to consider along the way, but let’s start with a very basic example to illustrate the concept.
Say you run some web application on www.example.com without any CSRF protection. To publish a message on their profile in the app, a user completes a simple HTML form and clicks the Submit button:
<form action="/action.php" method="post">Â Â Subject: <input type="text" name="subject"/><br/>Â Â Content: <input type="text" name="content"/><br/>Â Â <input type="submit" value="Submit"/></form>
The submit action causes the web browser to send a POST request to the server with whatever data the user entered being sent in the subject and content parameters:
POST /post.php HTTP/1.1Host: example.comsubject=I am feeling pretty good today&content=I just ate a cookie, chocolate chip
If the user is logged in and the attacker knows the request syntax, getting the user to click a specially crafted link to the attacker’s site could enable a CSRF attack to publish an ad on that user’s profile. The code hosted on the attacker’s site would cause the user’s browser to internally process and submit an HTML form similar to:
<form action="https://example.com/action.php" method="post"> Â <input type="text" name="subject" value="Buy my product!"/> Â <input type="text" name="content" value="To buy my product, visit this site: example.biz!"/> Â <input type="submit" value="Submit"/></form><script> Â document.forms[0].submit();</script>
If the site is vulnerable to CSRF, the user’s web browser will send a POST request similar to the following:
POST /post.php HTTP/1.1Host: example.comsubject=Buy my product!&content=To buy my product, visit this site: example.biz!
On an unprotected page, this could achieve CSRF and post the fake message on the user’s profile at example.com. This is because the server treats the forged request as coming from an authenticated user and doesn’t care or check what site it originated from. As long as the action was initiated by the user (to include their session cookie), it doesn’t matter where the code is hosted—which is why it’s called cross-site request forgery.
To prevent such attacks, you decide to protect your site using simple token-based CSRF mitigation. Your web server sets the token and sends it to the browser right after the user logs in, and all form submissions in your app include a hidden field containing that unique token. Assuming proper token generation and validation (see below), this should eliminate the CSRF vulnerability:
<form>Â Â Subject: <input type="text" name="subject"/><br/>Â Â Content: <input type="text" name="content"/><br/>Â Â <input type="submit" value="Submit"/>Â Â <input type="hidden" name="token" value="dGhpc3Nob3VsZGJlcmFuZG9t"/></form>
The server should then only accept POST requests from the user if they include this exact value of the token request parameter, for example:
POST /post.php HTTP/1.1Host: example.comsubject=I am feeling pretty good today&content=I just ate a cookie, chocolate chip&token=dGhpc3Nob3VsZGJlcmFuZG9t
With this protection in place, an attacker who tries to perform CSRF via a malicious site cannot fake HTTP requests because they don’t know the current token set in the valid user’s cookie. And because your server now rejects all requests without this token, any CSRF attack attempts will fail.
There are many different ways of generating and checking anti-CSRF tokens depending on your application requirements. First and foremost, if your application framework or programming language already includes CSRF prevention functionality, it is usually best to rely on this or find a reputable external library rather than trying to implement your own. Whichever synchronizer token pattern you choose, make sure your tokens and their use meet several basic requirements:
SameSite
cookies to further mitigate cross-site attacks. In addition, using the HTTPOnly
attribute can prevent JavaScript access to cookies.With a basic anti-CSRF token similar to the one shown above, you set the token in the user session cookie upon login and verify that same token for every form during the active session. In many cases, this level of CSRF protection could be enough, especially if you have session time-out limits, but some use cases may need a more secure approach.
To balance security and usability, you can generate a separate token for each form you use.
To do this, generate a token but do not expose it directly to the user’s browser. Instead, hash the token combined with the filename of the form, for example:
hash_hmac('sha256', 'post.php', $_SESSION['internal_token'])
To verify, compare the two hashes generated in this way. If the token is valid and the same form was used, the hashes will match.
When a very high level of protection is required, perhaps in a banking application, you can use a separate token for each request simply by invalidating every token after it is verified. Implementing per-request tokens has several usability drawbacks that you should carefully consider:
Normally, each token is stored on the server for verification, allowing the server to remember the session state. In some cases, like if your web page or application is very busy and/or you have limited server storage, you may want to use stateless CSRF protection to eliminate the need to store tokens on the server side. The easiest way to do this is using the double-submit cookie pattern, either signed or unsigned (what the OWASP CSRF Prevention Cheat Sheet calls the “naive” double-submit cookie).
The idea behind double-submit cookies is that the server sets a random value in a cookie before the user even authenticates. The server then expects this value to be sent with every request, for example by using a hidden form field. The “naive” pattern relies just on having a random value that is unguessable to an attacker, while the signed pattern additionally encrypts this value using a server-side secret key. Some implementations also use timestamps as part of the token generation and verification process.
Many modern apps rely on Ajax requests instead of traditional form tags and submissions, which can make implementing typical anti-CSRF tokens tricky. An alternative method is to use a custom request header appended by the client to requests that need CSRF protection. The header can be any key-pair value that doesn’t conflict with existing headers. Where this custom header is used, the backend will reject any requests without that HTTP header. Note that an overly lax CORS (cross-origin resource sharing) setting may allow an attacker to set cookies as well as custom headers, so be careful to restrict this to origins that you definitely control.
As an example of applying CSRF protection by default, you can override the prototypical XMLHttpRequest.open()
JavaScript method with one that automatically sets a custom anti-CSRF header. Similar mechanisms are available in popular libraries and frameworks.
Some older online resources advise against using anti-CSRF tokens for API endpoints as unnecessary—but with so many websites and applications being entirely API-driven, this no longer holds true. As with Ajax requests, custom request headers are a good way to implement CSRF protection for APIs.
It’s a common misconception that anti-CSRF tokens are only needed when a user is logged in, so you don’t need CSRF protection for login forms. While it’s true you can’t directly impersonate a user before login, omitting CSRF protection for login forms may allow attacks that expose sensitive information after tricking the user into logging in as the attacker. An attack could be performed as follows:
To minimize the risk of such attacks, it’s best to use some type of CSRF protection on login pages as well.
While anti-CSRF tokens can—when implemented correctly—provide solid protection against CSRF attacks, having other vulnerabilities in your application may allow attackers to bypass some CSRF protection measures. Most importantly, if your web application has a cross-site scripting (XSS) vulnerability, an attacker may be able to use XSS to run a script that silently fetches a new version of a form complete with a current (valid) CSRF token, allowing them to perform CSRF for that user session.Â
To prevent this and maintain a good web application security posture overall, make sure you regularly scan your websites, applications, and APIs for all types of vulnerabilities, not just CSRF.