Consent

This site uses third party services that need your consent.

Skip to content
Steven Roland
  • Simple PHP CSRF Token

    Cross-Site Request Forgery (CSRF) is a type of security vulnerability where an attacker tricks a user into performing actions on a web application where the user is authenticated. This could be anything from changing account settings to initiating financial transactions, all without the user being aware of what's happening.

    Many popular frameworks today include CSRF protections out of the box and requires little to no additional code to set up. But if you aren't using a modern framework, or you're just interesting in learning more about how CSRF attacks can be caught and handled, keep reading. We're going to explore how CSRF attacks can happen and write some simple PHP code from scratch that will help prevent them.

    How does a CSRF attack happen?

    CSRF attacks take advantage of authenticated website sessions, tricking a user's browser into performing actions without the user's knowledge or consent. I remember many years ago, when CSRF vulnerabilities were becoming more visible, the common advice given was to log out as soon as you were done using a website and before you closed your browser. This was an effort to minimize your attack surface because if you weren't logged in, the likelihood that anything detrimental could happen to your account would be decreased significantly. It's more common today that website developers implement CSRF protections, so while logging out is still probably a good idea, it's not as vital to maintaining your account heath.

    For an example of how a CSRF might happen, think in terms of steps like this hypothetical scenario:

    1. You open up your web browser and log into your bank's website to view your account.

    2. Still with your bank's website open in the first tab, you then open a second browser tab. Let's say you're heading to Google because you need to look up the bank's customer service contact information.

    3. You type in your search and scroll through results and you click one that looks promising. Great, you think you found what you needed.. But, unknown to you, that link you clicked took you to a website specifically created by someone with malicious intent.

    4. Now that you've clicked the link and loaded the website the attack can begin. Sometimes it's just that quick, and all you did was click a link to go to a website. Sometimes it's a little more complex, you might have to click a button or type in some information.

    5. Behind the scenes, the malicious website you visited is attempting to submit forms -- maybe a wire transfer form, for example -- on your bank's website. And since you were still logged in, your bank's website thinks that you're the one submitting these forms to move your money around.

    Now, if the bank's website developers have set up CSRF protections like we'll discuss in just a minute, forms submitted from the malicious website should not be processed and your money should stay safe and sound.

    What's a CSRF Token

    CSRF tokens, or anti-CSRF tokens, are a security measure used to prevent Cross-Site Request Forgery (CSRF) attacks. They work by ensuring that a submitted request is only accepted by a web application if it contains a unique, secret string that the server expects.

    In the hypothetical scenario described above, your bank's website would check for a CSRF token that should be included with every form submission. If the token is missing or not what the bank's website expects, it'll stop trying to process the submission and may throw a validation error or simply fail silently. Think of CSRF tokens as the key to a locked box containing whatever information needed to do your next task. Without the key, and without the correct key specifically, you can't open the box and get to the information.

    Making a token

    Let's start writing some code. In most cases, you should use a library for generating and validating tokens. However, it's completely possible to come up with your own solution for small to medium-sized applications.

    As mentioned above, a token is a unique, secret string that the server expects. So let's figure out how to generate a unique, random string first.

    For this, you might think about using md5() to create a hash, and that's certainly an option -- one that many people have used for many years -- but it's not a great option. I've seen md5(uniqid(rand(), true)) around the internet a lot and in older projects I've worked on personally. Really quick, here's why this is a bad idea for such a foundational piece of website security:

    • rand() is predictable

    • uniqid() only adds up to 29 bits of entropy

    • md5() doesn't add entropy, it just mixes it deterministically

    Instead, for a simple token with PHP 7+, let's use bin2hex() and random_bytes() like this:

    bin2hex(random_bytes(32));

    Storing the token

    Now that you know how to make a token, where should you store it? Well, since you'll need it across requests, storing the token in session sounds like the most appropriate option. Let's do that:

    <?php
    
    // START THE SESSION
    session_start();
    
    // SET THE TOKEN IN THE SESSION IF A TOKEN DOES NOT ALREADY EXIST
    if (empty($_SESSION['csrf_token'])) {
         $_SESSION['csrf_toekn'] = bin2hex(random_bytes(32));
    }

    With the token stored in session, you'll be able to send it through a form submission and compare the token received on the other end to the token stored.

    Using the token

    So far we've talked about how to make and store a token for CSRF protection, but now let's take a look at how to use it in a form that a user on your website might need to submit.

    form.php

    <?php
    
    // START THE SESSION
    session_start();
    
    // SET THE TOKEN IN THE SESSION IF A TOKEN DOES NOT ALREADY EXIST
    if (empty($_SESSION['csrf_token'])) {
         $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    } ?>
    
    <form method="POST" action="form-submit.php">
         <input type="email" name="email" required />
         <!-- ADD CSRF TOKEN INPUT HERE 👇 👇 👇 -->
         <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?? '' ?>" />
         <button type="submit">Submit</button>
    </form>

    CSRF tokens are typically found in hidden a form input so that the token is quietly passed with the information that the user provided when the form is submitted.

    Checking the token

    Once the user submits the form, you'll need to catch all the form data and check the value of the CSRF token before handling the data that the user provided. Notice in the example code above that the form submits to another PHP file on the server, form-submit.php. Let's take a look at what that might look like:

    form-submit.php

    <?php
    
    // START THE SESSION
    session_start();
    
    // CHECK THAT THE TOKEN WAS PASSED FROM THE FORM AND EXISTS IN THE SESSION
    if (! isset($_POST['csrf_token']) || ! isset($_SESSION['csrf_token'])) {
         header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
         exit;
    }
    
    // COMPARE THE SESSION TOKEN AND THE TOKEN PASSED FROM THE FORM
    if (! hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
         header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
         exit;
    }
    
    // CONTINUE PROCESSING THE FORM DATA // ...

    Breaking this part down a little bit -- after starting the session, we're checking that the token is set in both the form data that was submitted and in the session. Then, if both are set, we check that the two tokens are equal. If either of these checks fail, we return the HTTP status code 405 (method not allowed) to the client using the header() function and immediately stop the script.

    Wrapping up

    I really hope you enjoyed this post and found it helpful! That's basically it for implementing really simple CSRF protection that does the trick. There are some other ideas to explore like submitting the form with javascript, per-form tokens, and single-use tokens -- I'll cover those things in some follow-up posts.

    More posts