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

v2.0.1 2025-10-12 23:21 UTC

This package is auto-updated.

Last update: 2025-10-12 23:22:14 UTC


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\Session helper (or any compatible helper exposing read, write, and unsetSessionValue).

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)

  1. Create a session helper and CSRF guard:
use JoeFallon\PhpSession\Session;
use JoeFallon\PhpCsrf\CsrfGuard;

$session = new Session();
$guard = new CsrfGuard('contact_form', $session);
  1. 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) . "\" />";
  1. 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 CsrfGuard instance is tied to a form name (string). If you have multiple forms on a page, use distinct names: new CsrfGuard('login_form', $session) and new 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 InvalidArgumentException for 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 RuntimeException if secure randomness cannot be obtained.
    • isValidToken(string $token): bool

      • Validates the supplied token against the session-stored value.
      • Throws InvalidArgumentException if 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 php requirement).
  • Depends on joefallon/phpsession for 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.

  1. Install dependencies (including dev dependencies):
composer install
  1. 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 in require-dev to remain compatible with PHP 7.4.
  • A phpunit.xml.dist file is included at the project root and bootstraps vendor/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

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.