Skip to Content
Course content

161: Building CSRF-Protected Forms

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you walk into your favorite local bank. You've already shown your ID at the door, so the staff knows exactly who you are. Now, imagine a prankster stands outside the window and manages to slide a piece of paper through the slot that says, "Transfer $500 to the prankster's account." Because you're already "verified" in the building, the teller might just see the request and execute it, thinking you're the one who asked.

That’s exactly how a Cross-Site Request Forgery (CSRF) attack works. The "bank" is your web server, and the "verified user" is someone with an active session cookie. The attacker doesn't need to steal the user's password; they just need to trick the user's browser into sending a request to your server while the user is still logged in. Since the browser automatically attaches the session cookie, your server thinks the request is legitimate.

To stop this, we need a "transaction slip"—a unique, secret token that the server generates and requires for every state-changing request. If the request doesn't come with the correct slip, the server rejects it, even if the user is logged in.

Generating the Secret Handshake

The first thing we need to do is create a token that is impossible for an attacker to guess. I usually recommend random_bytes() for this because it's cryptographically secure. We store this token in the user's session so we can check it later.

session_start();

// Generate a token if one doesn't already exist for this session
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

I put this logic in a helper function or a global initialization file. You only need to generate it once per session, or you can rotate it after every single request if you're building something ultra-secure, like a banking app. For most projects, once per session is plenty.

Embedding the Token in Your Forms

Now that the server knows the secret, we have to give it to the client. We do this by dropping it into a hidden input field in our HTML form. The user never sees it, but it gets sent back to us when they hit "Submit."

<form action="update_profile.php" method="POST">
    <label for="email">New Email:</label>
    <input type="email" name="email" id="email">
    
    <!-- This is the magic piece -->
    <input type="hidden" name="csrf_token" value="">
    
    <button type="submit">Update Profile</button>
</form>

If an attacker tries to forge a request from a different website, they can't "reach into" your user's session to find this token. Their forged form will either be missing the token or contain a fake one, and that's where we catch them.

The Moment of Truth: Verification

When the POST request hits your server, you must verify the token before doing anything else. If the token is missing or doesn't match what's in the session, you stop the script immediately. I usually throw a 403 Forbidden error here because the request is fundamentally unauthorized.

session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        // Something is fishy. Kill the request.
        http_response_code(403);
        die("CSRF token validation failed. Request denied.");
    }

    // If we got here, the request is legitimate.
    // Now we can safely update the user's email.
    $new_email = $_POST['email'];
    // ... database logic here ...
}

One quick tip: always use the strict comparison operator (!==) when checking tokens. You don't want PHP's type juggling to accidentally let a "falsey" or numeric value pass through as a match.




📋 Practical Task

Securing a User Profile Email Update Form

You have been given a basic PHP profile update page that is currently vulnerable to CSRF attacks. Your task is to secure it.

Requirements:

  • Implement session_start() at the top of both the form page and the processing script.
  • Create a mechanism to generate a cryptographically secure CSRF token and store it in $_SESSION['csrf_token'] if it doesn't already exist.
  • Modify the HTML form to include a hidden input field containing this token.
  • In the processing script, write a check that compares the $_POST['csrf_token'] against the $_SESSION['csrf_token'].
  • Ensure the script terminates with a 403 Forbidden response if the tokens do not match or if the token is missing.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.