wilycode / form-csrf
Optional session-backed CSRF token field and validator for WilyCode Form.
Requires
- php: ^8.4
- wilycode/filter: ^0.1
- wilycode/form: ^0.1.1
- wilycode/session: ^0.1
- wilycode/validator: ^0.1
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Optional session-backed CSRF protection for WilyCode Form. Provides a transparent form field, a standalone validator, and a framework-independent token manager.
WilyCode PHP Ecosystem
Part of WilyCode — independent PHP 8.4+ packages with separate repositories, releases and Composer installations.
Requirements and installation
- PHP 8.4 or newer.
wilycode/formv0.1.1 or newer (within the^0.1series),wilycode/sessionandwilycode/validator.- A
SessionInterfaceimplementation such aswilycode/session-php.
Once the new repository has been published and tagged, install:
composer require wilycode/form-csrf:^0.1 wilycode/session-php:^0.1
This package does not force a PHP native session dependency. Install wilycode/form v0.1.1 or newer first; v0.1.0 does not support transparent fields.
Quick start
use WilyCode\Form\Form; use WilyCode\FormCsrf\CsrfTokenField; use WilyCode\FormCsrf\SessionCsrfTokenManager; use WilyCode\Session\Php\NativeSession; $manager = new SessionCsrfTokenManager(new NativeSession()); final class ContactForm extends Form { public function __construct(private readonly SessionCsrfTokenManager $manager) { parent::__construct(); } protected function init(): void { $this->add(new CsrfTokenField($this->manager, 'contact')); $this->addField('name'); $this->addField('email'); } } // Render GET: $form = new ContactForm($manager); $token = $form->getField('_csrf')->getToken(); // <input type="hidden" name="_csrf" value="(HTML-escaped token)"> // Process POST: supply the request parsed body through setRequest($request). $form = (new ContactForm($manager))->setRequest($request); if ($form->isSubmittedAndValid()) { $data = $form->getData(); // Contains name, email; not _csrf. // Persist $data. } elseif ($form->isSubmitted() && $form->getField('_csrf')->getErrors()->hasErrors()) { // Return HTTP 403 for an invalid or missing token. }
Form::getDataByName('_csrf') returns the submitted token; CsrfTokenField::getToken() returns the current server-generated token. Never use submitted data to populate the hidden input.
The base wilycode/form library already registers custom fields with add(Field|Fieldset|Collection $element). No changes to addField() are needed.
Behavior
- Cryptographically random 32-byte token encoded as a 64-character hex string.
- Stable for a session and form ID; different forms and sessions get different values.
- Constant-time comparison with
hash_equals(). - Validation never creates a token; missing stored tokens fail closed.
- Submitted tokens are validated as ordinary form fields; a missing token fails the required-field check, while a non-empty invalid token fails
CsrfTokenValidator(validation.csrf_token.invalid). - CSRF fields are transparent: validated normally but excluded from
getData()andgetDataWithExcludes().getDataByName()continues to work. - Default field-level filters are not inherited, to avoid silently normalizing a security token. General form-wide filters can still alter the input array, so avoid modifying the
_csrfkey in those filters. - CSRF fields cannot be made optional (
optional()throws), preventing accidental acceptance of a missing token. - Tokens are not one-time by default, so back navigation and multiple tabs remain usable. Regenerate the PHP session ID upon sign-in or privilege elevation; rotate the associated token namespace on authentication or privilege changes if you need to invalidate pre-login tokens (for example,
$session->withNamespace('csrf')->clear()).
Security notes
CSRF tokens protect cookie-authenticated unsafe HTTP requests from cross-site submission. Use HTTPS in production, HttpOnly/Secure/SameSite session cookies as appropriate, and do not leak tokens to URLs, logs or third-party scripts. CSRF tokens do not replace authorization, input validation or XSS protection. Do not cache HTML containing session-specific tokens in a public shared cache.
API
| Class | Purpose |
|---|---|
CsrfTokenField |
Required, transparent field with the CSRF validator and getToken() for rendering; cannot be optional |
CsrfTokenValidator |
Validates a submitted scalar token using the configured manager |
CsrfTokenManagerInterface |
Contract: getToken(formId), isValid(formId, token) |
SessionCsrfTokenManager |
Stores tokens in a csrf namespace of SessionInterface |
Development
composer install
composer validate --strict
composer test
License
MIT © 2026 Simon Piter. See LICENSE.