How to protect your websites and web apps with anti-CSRF tokens

Anti-CSRF tokens are used to protect against cross-site request forgery attacks. This post explains the basics of CSRF tokens and shows how to use them to protect the users of your websites and applications against CSRF.

How to protect your websites and web apps with anti-CSRF tokens

The most common way of preventing cross-site request forgery attacks (CSRF/XSRF) is to use an anti-CSRF token, which is simply a unique value set and then required by a web application. CSRF is a client-side attack that can be used to redirect users to a malicious website, steal sensitive information, or execute other actions within a user session. Fortunately, it’s relatively easy to use CSRF tokens to protect users against CSRF attacks and their consequences.

Anti-CSRF token basics

The idea behind anti-CSRF tokens (aka synchronizer token patterns or simply CSRF tokens) is give the user’s browser a piece of information (a token) that the browser then has to send back. The token must be unique and impossible to guess by a third party, and the application must only process HTTP requests once the token has been verified. This ensures that only the original user can send requests within an authenticated session.

For a basic example without CSRF protection, say you run a web application on www.example.com. To publish a message on their profile in the app, a user completes an 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 as parameters:

POST /post.php HTTP/1.1
Host: example.com

subject=I am feeling well&content=I just ate a cookie and it was delicious

If the user is logged in and the attacker knows the request syntax, it may be possible to use a CSRF attack to publish an ad on that user’s profile:

<form action="/action.php" method="post">
  Subject: <input type="text" name="subject" value="Buy my product!"/>
  Content: <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>

As a result, the web browser sends the following POST request:

POST /post.php HTTP/1.1
Host: example.com

subject=Buy my product!&content=To buy my product, visit this site: example.biz.

On an unprotected page, this could achieve CSRF if the server treats the forged request as coming from an authenticated user.

But now let’s say your site uses simple token-based CSRF mitigation, and your web server sets the token in a session cookie sent to the browser right after login. All the form submissions then include a hidden field containing the token. Assuming proper token validation, this completely eliminates 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="R6B7hoBQd0wfG5Y6qOXHPNm4b9WKsTq6Vy6Jssxb"/>
</form>

The server should then only accept POST requests from the same user that include this exact token value, for example:

POST /post.php HTTP/1.1
Host: example.com

subject=I am feeling well&content=I just ate a cookie and it was delicious.&token=R6B7hoBQd0wfG5Y6qOXHPNm4b9WKsTq6Vy6Jssxb

With this protection in place, an attacker who tries to perform CSRF using a malicious site cannot fake HTTP requests without knowing the current token set in the valid user’s cookie. Because your server rejects all requests without this token, any attack attempts will fail.

How to generate and verify CSRF tokens

Whatever specific methods you use to generate and verify anti-CSRF tokens, make sure you follow these overriding security rules to prevent attackers from forging tokens in their malicious requests:

  • Use a reputable and non-predictable random number generator with sufficient entropy.
  • Expire tokens after a short time to prevent reuse.
  • Use secure comparison methods (e.g. compare cryptographic hashes) when checking if the received token is the same as the set token.
  • Never send CSRF tokens in HTTP GET requests to ensure that they are never shown in the URL and cannot leak in the Referer header with other referrer information.

For example, in PHP you can generate a basic token as follows:

$_SESSION['token'] = bin2hex(random_bytes(24));

When you receive an incoming token, you can securely verify it by comparing hashes:

if (hash_equals($_SESSION['token'], $_POST['token'])) {
  // Action if token is valid
} else {
  // Action if token is invalid
}

CSRF protection for each form

With the basic anti-CSRF token described above, you set the token in the user session cookie upon login and then verify that same token for every form. In most cases, this protection is enough, but some sites may require 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.

CSRF protection for each request

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. This approach has several usability drawbacks that should be carefully considered before implementing per-request tokens. Most notably, it makes it impossible to use the app in multiple tabs. You also can’t use the browser’s back button, only the app’s internal navigation features. Because each request needs a new random token, per-request protection needs to consider server performance and use less resource-intensive random number generators.

Using non-persisted CSRF tokens

If your web page or application is very busy and you have limited server storage, you may want to completely avoid persisting tokens on the server side. In these specific cases, you can generate and process tokens cryptographically without having to store them in the server session:

  1. Use symmetric encryption with a key that is only known to the server and never shared with clients.
  2. Generate the token by combining the current timestamp, user name, and form name (if needed), and then encrypt the whole string using the server key.
  3. When you receive a token from the web browser, decrypt it using that same key.
  4. Check the timestamp from the decrypted token (to eliminate old tokens), and compare the decrypted user and form names with the expected current values.

While this approach doesn’t require server-side storage, it may have some performance overhead because cryptographic functions are more resource-consuming than simple random number generation.

Another option for implementing non-persistent tokens are double-submit cookies. For this technique, the server sets a random value in a cookie even before the user authenticates. The server then expects this value to be sent with every request (for example, using a hidden form field).

CSRF protection for asynchronous (Ajax) requests

Anti-CSRF tokens should also be used for Ajax requests. To implement them securely, first make sure that your web server does not allow cross-domain asynchronous requests (by checking for cross-origin resource sharing headers).

When implementing CSRF protection for Ajax requests, you can include your token in a hidden text field as normal or put it directly in JavaScript. You then send the token with each async request and verify its presence and value on the server side.

Anti-CSRF tokens for login forms

It is a common belief that anti-CSRF tokens are only needed when a user is logged in and therefore you don’t need CSRF protection for login forms. While it’s true you cannot impersonate the user before they have logged in, a lack of 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:

  1. The attacker creates an account in your web application.
  2. The attacker tricks a victim into logging in to your app but using the attacker’s credentials, which may be possible with some social engineering if there is no CSRF protection on the login form.
  3. The victim uses your application as normal, potentially not knowing they are logged in as someone else.
  4. The attacker may then stalk the victim using history functionality or profit from the victim’s interactions with your app in other ways.

To minimize the risk of these and related attacks, it is best practice to include anti-CSRF tokens on all login pages as well.

As with login forms, you may also see online resources that advise against using anti-CSRF tokens for REST API endpoints, claiming they are unnecessary. While this may be technically true in many cases, it’s often hard to predict all the ways that an API will be accessed (and potentially modified in the future), so having CSRF protection for REST APIs may be seen as an extra layer of security.

Beyond tokens: Other CSRF prevention methods

For an in-depth defense against CSRF, you can combine CSRF tokens with other techniques. For example, to verify Ajax requests, you could add and then check an arbitrary custom header. This works because same-origin policy dictates that only JavaScript from the same origin can be used to add request headers, though note that you should not rely on this behavior as your only line of defense. For a detailed discussion of this and other CSRF prevention methods, see the OWASP Cross-Site Request Forgery Prevention Cheat Sheet.

Anti-CSRF tokens are one of the safest ways to defend against CSRF attacks, but other vulnerabilities in your application may allow attackers to bypass CSRF protection. For example, if your web application has a cross-site scripting vulnerability (XSS), an attacker may use XSS to execute a script that silently fetches a new version of the form with the current (valid) CSRF token. To prevent this and maintain solid web application security, make sure you regularly scan your web applications for all types of vulnerabilities, not just CSRF.

Zbigniew Banach

About the Author

Zbigniew Banach - Technical Content Lead & Managing Editor

Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.