CSRF Token in PHP (Very Simple Example)

Welcome to a quick tutorial on how to implement CSRF token protection in PHP. Want to further secure your website, or just stumbled on this CSRF “token thing” on the Internet? Well, it stands for Cross-Site Request Forgery, and it is nothing more than a random string in the session.

// (A) START SESSION & GENERATE RANDOM TOKEN
session_start();
$_SESSION["token"] = bin2hex(random_bytes(32));

// (B) EMBED TOKEN INTO HTML FORM
<input type="hidden" name="token" value="<?=$_SESSION["token"]?>">

// (C) ON FORM SUBMIT, CHECK SUBMITTED TOKEN AGAINST SESSION
if (isset($_POST["token"]) && isset($_SESSION["token"]) && $_POST["token"]==$_SESSION["token"]) { PROCEED }

That’s all for the basics. But just what is CSRF? How will this token prevent a CSRF attack? Read on to find out!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

 

WHAT IS CSRF?

For this section, we are going to explain what the heck is cross-site request forgery (CSRF), and why you should be worried. For you guys who already know what it is, feel free to skip this section.

 

SIMPLE EXAMPLE OF A CSRF ATTACK

THE LEGIT SITE

You are currently signed in at “legit site” and it has a form where you can delete your account. All you need to do is enter “CONFIRM” and submit it:

http://legit-site.com/my-account.php
<form action="http://legit-site.com/delete.php" method="POST">
  <p>Type in "CONFIRM" below to delete your account</p>
  <input type="text" name="confirmation">
  <input type="submit" value="DELETE ACCOUNT">
</form>

 

THE BAD SITE

On another “fake site” that evil code ninjas have created, they wrote a viral article to bait people into writing comments. But the comments form actually submits “CONFIRM” to delete your account on the “legit site”:

http://fake-site.com/fake-article.php
<!-- FAKE COMMENT FORM THAT SUBMITS TO DELETE ACCOUNT ON "LEGIT SITE" -->
<form action="http://legit-site.com/delete.php" method="POST">
  <input type="hidden" name="confirmation" value="CONFIRM">
  Name - <input type="text">
  Message - <input type="text">
  <input type="submit" value="Reply">
</form>

Will this method actually work? Yes, if the security at the “legit site” is dumb enough. Since you are already signed into the “legit site”, it will probably be taken as a legit request to delete your account.

 

 

A LOT OF TROUBLE

This is only a small example of how CSRF attacks work. So what if the “legit site” is an eCommerce website? That will mean hackers may be able to purchase stuff using your account, without your knowledge. What can be worse? What if this attack is done on a money transfer website? I think you get the picture now, and this is why CSRF prevention is such a big deal.

 

PHP CSRF TOKEN

Thankfully, modern technology and firewalls have become smarter to detect CSRF attacks – Firewalls and servers can be configured to ignore requests that originate from other websites. But we cannot count on that single layer of security to be 100% fool-proof, and thus the purpose of the CSRF token.

 

 TUTORIAL VIDEO

 

STEP 1) INSERT RANDOM CSRF TOKEN INTO FORM

1-generate-token.php
<?php 
// (A) GENERATE CSRF TOKEN (RANDOM STRING INTO SESSION)
// CREDITS : https://stackoverflow.com/questions/6287903/how-to-properly-add-csrf-token-using-php
session_start();
$_SESSION["token"] = bin2hex(random_bytes(32));
$_SESSION["token-expire"] = time() + 3600; // 1 hour = 3600 secs
 
// (B) EMBED TOKEN INTO FORM ?>
<form method="post" action="2-submit.php">
  <input type="hidden" name="token" value="<?=$_SESSION["token"]?>">
  <input type="email" name="email" value="jon@doe.com">
  <input type="submit" value="Go!">
</form>

Yes, this is just a “regular HTML form”. But take note that we generate a random string (token) in $_SESSION["token"], and insert it into a hidden form field.

 

 

STEP 2) VERIFY TOKEN ON FORM SUBMISSION

2-verify-token.php
<?php
// (A) START SESSION
session_start();

// (B) COUNTER CHECK SUBMITTED TOKEN AGAINST SESSION
if (
  isset($_POST["token"]) &&
  isset($_SESSION["token"]) &&
  isset($_SESSION["token-expire"]) &&
  $_SESSION["token"]==$_POST["token"]
) {
  // (B1) EXPIRED
  if (time() >= $_SESSION["token-expire"]) {
    exit("Token expired. Please reload form.");
  }
 
  // (B2) OK - DO YOUR PROCESSING
  unset($_SESSION["token"]);
  unset($_SESSION["token-expire"]);
  echo "OK";
}

// (C) INVALID TOKEN
else { exit("INVALID TOKEN"); }

That’s all for the “rocket science” CSRF token. How the heck does a random string in the session prevent forged requests?

  • Very simply, only the server ($_SESSION["token"]) and the user (<input name="token">) has the token.
  • The request will only proceed if the token is validated – if ($_SESSION["token"]==$_POST["token"]) { ... }
  • In other words – To forge a request, the “bad site” has to somehow get hold of the token.

This should be straightforward enough, but take extra note of the part “bad site gets hold of the token”. Yes, the protection is broken when the token is leaked. This is why we set an expiry ($_SESSION["token-expire"]) to reduce the risk of a leaked token; Very secure sites set the expiry to a few minutes, giving hackers very little time to even try to get the token.

 

 

EXTRA) HOW ABOUT AJAX FORMS?

3-ajax-form.php
<?php
// (A) GENERATE CSRF TOKEN (RANDOM STRING INTO SESSION)
session_start();
$_SESSION["token"] = bin2hex(random_bytes(32));
$_SESSION["token-expire"] = time() + 3600; // 1 hour = 3600 secs
 
// (B) EMBED TOKEN INTO FORM AS USUAL ?>
<form id="myForm" onsubmit="return doAJAX()">
  <input type="hidden" name="token" value="<?=$_SESSION["token"]?>">
  <input type="email" name="email" value="jon@doe.com">
  <input type="submit" value="Go!">
</form>
 
<script>
function doAJAX () {
  // (C) GET FORM DATA
  var data = new FormData(document.getElementById("myForm"));
 
  // (D) AJAX FETCH
  fetch("2-submit.php", { method: "POST", body: data })
  .then(res => res.text())
  .then(res => {
    // DO WHATEVER YOU NEED
    console.log(res);
    // if (res=="OK") { ... }
  })
  .catch(err => console.error(err));
  return false;
}
</script> 

Nothing special in particular. Just generate the token and send it as-it-is.

 

 

EXTRAS

That’s all for this guide, and here is a small section on some extras and links that may be useful to you.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this short tutorial. I hope it will help you make more secure sites in the future, and if you have anything to add to this tutorial, please feel free to comment below. Good luck, and happy coding!

23 thoughts on “CSRF Token in PHP (Very Simple Example)”

  1. For example: the user opens two different pages, and user can be on both pages send Comment ….
    Token reset after each post?
    Do we need some tokens for each page?
    I think tokens should be like arrays and need check the requesting page… . Is that right?

    1. That is called over-thinking. 😆 Generate random token, set an expire time. Use it for everything until it expires, then regenerate. That’s all – If security is a big concern, set a short expiry time as above.

    1. Of course, and the steps are the same – Start the session, create a random token, put the hidden token into the form, then check the token as the form is being submitted.

    1. It does not matter, just fall back to an HTML-only form. As long as the token get passed back to the server and you can verify it. You will only get a problem when the user disables the cookie, which, there is nothing you can do about it.

    2. Thank you for the quick reply.
      From your code, there seems to be no setcookie command to write to the cookie(stored on the client side ) ? Or is it that there is no need to write the token value to the cookie ? As its just kept in the session file ( server side ) ?

      Thanks.

    3. Go through the cookie vs session tutorial again and slowly trace through the entire process if you are still confused.

      1) session_start() will generate a cookie, set a unique session ID, AND create a temporary session data file on the server.
      2) $_SESSION[‘token’] = “RANDOM” is kept on the server.
      3) The cookie with session ID is passed to the client – But whether the user’s device choose to keep or discard the cookie is beyond our control.
      4) On return visits, the client will send the cookie back to the server.
      5) session_start() will then use the cookie to tie back to the session ID, retrieve the temporary data file on the server.
      6) Without the cookie, there is no way to tie back to the session ID. Without the session ID, there is no way to retrieve the CSRF token. Without the CSRF token, there is no way we can verify. The system falls apart.

      Nothing to do with Javascript. We don’t want to keep the CSRF token in the cookie.

Leave a Comment

Your email address will not be published. Required fields are marked *