joefallon / phpcsrf
PhpCsrf is a simple library for cross-site request forgery prevention.
Installs: 21
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/joefallon/phpcsrf
Requires
- php: >=7.4.0
- ext-openssl: *
- joefallon/phpsession: ^5.0
Requires (Dev)
- phpunit/phpunit: ^9.6
README
Simple, secure, well-tested CSRF protection for PHP applications.
PhpCsrf provides a tiny, easy-to-audit API for generating and validating anti-CSRF tokens on a per-form basis. Tokens are cryptographically secure, single-use, and intentionally minimal so you can understand and integrate quickly.
Why use PhpCsrf?
- Small, dependency-light library (works with the included Session helper).
- Cryptographically secure tokens (uses PHP's CSPRNG via random_bytes()).
- Tokens are single-use to prevent replay attacks.
- Clear, well-documented API — easy to review and maintain.
- Includes unit tests so you can trust the behaviour.
Quick facts
- Token entropy: 256 bits (32 random bytes, hex-encoded to 64 chars).
- PHP: supports PHP >= 7.4 (see composer.json).
- Session backend: relies on the included
JoeFallon\PhpSession\Sessionhelper (or any compatible helper exposingread,write, andunsetSessionValue).
Installation
Install with Composer:
composer require joefallon/phpcsrf
This package declares joefallon/phpsession as a dependency. Make sure your
project also meets the PHP version requirement (>= 7.4).
Basic usage (quick start)
- Create a session helper and CSRF guard:
use JoeFallon\PhpSession\Session; use JoeFallon\PhpCsrf\CsrfGuard; $session = new Session(); $guard = new CsrfGuard('contact_form', $session);
- When rendering the form, generate a token and include it in the markup:
$token = $guard->generateToken(); // Print in a hidden input (escape for HTML) echo "<input type=\"hidden\" name=\"csrf_token\" value=\"" . htmlspecialchars($token, ENT_QUOTES | ENT_HTML5) . "\" />";
- When processing the form submission, validate the token:
$submitted = $_POST['csrf_token'] ?? ''; try { if ($guard->isValidToken((string)$submitted)) { // Token valid — process the form } else { // Token invalid — reject the request http_response_code(403); echo 'Invalid CSRF token.'; } } catch (InvalidArgumentException $e) { // Token was empty or invalid input http_response_code(400); echo 'Bad request.'; } catch (RuntimeException $e) { // Failure generating secure randomness (rare) — treat as server error http_response_code(500); echo 'Server error.'; }
Important behaviour and notes
-
Single-use tokens:
isValidToken()removes the token from the session when called. A token that validates once will not validate again. This defends against replay attacks but means you must regenerate a token for each form render. -
One token per form name: each
CsrfGuardinstance is tied to a form name (string). If you have multiple forms on a page, use distinct names:new CsrfGuard('login_form', $session)andnew CsrfGuard('comment_form', $session). -
generateToken()overwrites any previously stored token for that form name. If you call it multiple times, only the most recent token is valid until consumed. -
Tokens are hex strings (64 chars) and safe to store in session data.
AJAX / Single Page Apps
For XHR/fetch requests you can expose a token via a small endpoint and send the
value in a custom header (e.g. X-CSRF-Token) or in the request body.
Example: endpoint that returns a token as JSON
// token-endpoint.php $session = new \JoeFallon\PhpSession\Session(); $guard = new \JoeFallon\PhpCsrf\CsrfGuard('ajax_form', $session); $token = $guard->generateToken(); header('Content-Type: application/json'); echo json_encode(['csrf_token' => $token]);
Client-side (fetch):
fetch('/token-endpoint.php') .then(r => r.json()) .then(data => { // include data.csrf_token as a header or in the request body for subsequent requests fetch('/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': data.csrf_token }, body: JSON.stringify({ /* payload */ }) }); });
Security guidance (do this in your app)
-
Use HTTPS for your site always. Session cookies and tokens should only be transmitted over TLS.
-
Configure session cookies with secure flags. Example (set before session start):
session_set_cookie_params([ 'secure' => true, // send only over HTTPS 'httponly' => true, // deny access from JavaScript (mitigates XSS token theft) 'samesite' => 'Lax', // consider 'Strict' if appropriate for your UX ]); session_start();
-
Prevent XSS. If an attacker can run JavaScript in a user's page they can read tokens and session identifiers, which defeats CSRF protection. Use content security policies, proper escaping, and input validation.
-
Protect session IDs: avoid exposing session identifiers in URLs, logs, or referrers. Use
session_regenerate_id(true)on privilege changes (e.g. after login) and follow secure session management practices.
API reference
-
class:
\JoeFallon\PhpCsrf\CsrfGuard-
__construct(string $formName, \JoeFallon\PhpSession\Session $session)
- $formName: non-empty unique name for the form/intent.
- $session: session helper (must implement
read,write,unsetSessionValue). - Throws
InvalidArgumentExceptionfor empty names.
-
generateToken(): string
- Generates a new token, stores it in the session, and returns the hex-encoded token string (64 chars when built with current defaults).
- Throws
RuntimeExceptionif secure randomness cannot be obtained.
-
isValidToken(string $token): bool
- Validates the supplied token against the session-stored value.
- Throws
InvalidArgumentExceptionif the provided token is empty. - Returns true if the token matches; false otherwise.
- Removes the token from the session when called (single-use behaviour).
-
Compatibility
- PHP 7.4 or later (see composer.json
phprequirement). - Depends on
joefallon/phpsessionfor session helper functionality.
Testing
This repository includes unit tests. The project was migrated from the legacy joefallon/kisstest runner to PHPUnit 9.6 (the last PHPUnit version that supports PHP 7.4). The migration checklist and notes are stored in MIGRATION_CHECKLIST.md.
Use the following steps to run the test suite locally.
- Install dependencies (including dev dependencies):
composer install
- Run the tests using the PHPUnit binary installed by Composer. Examples:
On Windows (cmd.exe):
vendor\bin\phpunit.bat -c phpunit.xml.dist
or (PowerShell / generic):
.\vendor\bin\phpunit -c phpunit.xml.dist
On Unix-like shells:
./vendor/bin/phpunit -c phpunit.xml.dist
Notes
- The project uses
phpunit/phpunit^9.6 inrequire-devto remain compatible with PHP 7.4. - A
phpunit.xml.distfile is included at the project root and bootstrapsvendor/autoload.php. - If you previously ran
php tests/index.php(the old KissTest runner), that file has been kept as a benign reference but no longer executes a test runner.
Contributing
Contributions are welcome. Please follow these guidelines:
- Keep changes small and focused.
- Add unit tests for new behaviour or bug fixes.
- Follow PSR-12 / project coding style.
- Open an issue describing the change before large refactors.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contact and links
- Project: https://github.com/joefallon/phpcsrf
- Author: Joe Fallon (joseph.t.fallon@gmail.com)
Acknowledgements
This project uses the joefallon/phpsession helper for session management. Tests are now executed with phpunit/phpunit (9.6) after migration from the project's former KissTest-based runner.