php 51 lines · 8 steps

Building a per-form CSRF guard in PHP

A small class that mints, embeds, and verifies one-time CSRF tokens scoped to individual forms.

Explained by highlit
1<?php
2 
3final class CsrfGuard
4{
5 private const SESSION_KEY = '_csrf_tokens';
6 private const MAX_TOKENS = 10;
7 
8 public function token(string $form = 'default'): string
9 {
10 if (session_status() !== PHP_SESSION_ACTIVE) {
11 session_start();
12 }
13 
14 $token = bin2hex(random_bytes(32));
15 $tokens = $_SESSION[self::SESSION_KEY] ?? [];
16 $tokens[$form] = $token;
17 
18 if (count($tokens) > self::MAX_TOKENS) {
19 $tokens = array_slice($tokens, -self::MAX_TOKENS, null, true);
20 }
21 
22 $_SESSION[self::SESSION_KEY] = $tokens;
23 
24 return $token;
25 }
26 
27 public function field(string $form = 'default'): string
28 {
29 $value = htmlspecialchars($this->token($form), ENT_QUOTES, 'UTF-8');
30 $name = htmlspecialchars($form, ENT_QUOTES, 'UTF-8');
31 
32 return sprintf(
33 '<input type="hidden" name="_csrf" value="%s"><input type="hidden" name="_csrf_form" value="%s">',
34 $value,
35 $name
36 );
37 }
38 
39 public function validate(string $submitted, string $form = 'default'): bool
40 {
41 $expected = $_SESSION[self::SESSION_KEY][$form] ?? null;
42 
43 if ($expected === null || $submitted === '') {
44 return false;
45 }
46 
47 unset($_SESSION[self::SESSION_KEY][$form]);
48 
49 return hash_equals($expected, $submitted);
50 }
51}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1CSRF tokens should be unpredictable, stored server-side, and bound to the specific form they protect.
  2. 2Comparing secrets with hash_equals avoids timing leaks that a plain equality check would expose.
  3. 3Consuming a token on validation makes it single-use, blocking replay of a stolen form submission.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a per-form CSRF guard in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code