|
| 1 | +# CSRF protection in forms |
| 2 | + |
| 3 | +A Cross-Site Request Forgery (CSRF) attack is a type of security vulnerability that tricks a user into performing |
| 4 | +actions on a web application in which they are authenticated, without their knowledge or consent. |
| 5 | + |
| 6 | +Web applications can protect users against these types of attacks by implementing CSRF tokens in their forms which are |
| 7 | +known only to the application that generated them and must be included when submitting forms. With each visit, a new |
| 8 | +CSRF token is added to the form so tokens are not reusable between forms. Missing to provide a valid CSRF token will |
| 9 | +result in a form validation error. |
| 10 | + |
| 11 | +## Implement CSRF protection |
| 12 | + |
| 13 | +Implementing CSRF protection requires three steps: |
| 14 | + |
| 15 | +- create new field using [laminas/laminas-form](https://github.com/laminas/laminas-form)'s [CSRF](https://github.com/laminas/laminas-form/blob/3.21.x/src/Element/Csrf.php) element |
| 16 | +- validate new field using [laminas/laminas-session](https://github.com/laminas/laminas-session)'s |
| 17 | +[CSRF](https://github.com/laminas/laminas-session/blob/2.22.x/src/Validator/Csrf.php) validator |
| 18 | +- render field using [laminas/laminas-form](https://github.com/laminas/laminas-form)'s [FormElement](https://github.com/laminas/laminas-form/blob/3.21.x/src/View/Helper/FormElement.php) helper |
| 19 | + |
| 20 | +### Create field |
| 21 | + |
| 22 | +Open the form's PHP class and append the following code to the method that initializes the fields (usually `init`): |
| 23 | + |
| 24 | +```php |
| 25 | +$this->add(new \Laminas\Form\Element\Csrf('exampleCsrf', [ |
| 26 | + 'csrf_options' => [ |
| 27 | + 'timeout' => 3600, |
| 28 | + 'session' => new \Laminas\Session\Container(), |
| 29 | + ], |
| 30 | +])); |
| 31 | +``` |
| 32 | + |
| 33 | +where `exampleCsrf` should be a suggestive name that describes the purpose of the field (example: `forgotPasswordCsrf`). |
| 34 | + |
| 35 | +### Validate field |
| 36 | + |
| 37 | +Open the InputFilter that validates the form fields and append the following code to the method that initializes the |
| 38 | +fields (usually `init`): |
| 39 | + |
| 40 | +```php |
| 41 | +$csrf = new \Laminas\InputFilter\Input('exampleCsrf'); |
| 42 | +$csrf->setRequired(true); |
| 43 | +$csrf->getFilterChain() |
| 44 | + ->attachByName(\Laminas\Filter\StringTrim::class) |
| 45 | + ->attachByName(\Laminas\Filter\StripTags::class); |
| 46 | +$csrf->getValidatorChain() |
| 47 | + ->attachByName(\Laminas\Validator\NotEmpty::class, [ |
| 48 | + 'message' => '<b>CSRF</b> is required and cannot be empty', |
| 49 | + ], true) |
| 50 | + ->attachByName(\Laminas\Session\Validator\Csrf::class, [ |
| 51 | + 'name' => 'exampleCsrf', |
| 52 | + 'message' => '<b>CSRF</b> is invalid', |
| 53 | + 'session' => new \Laminas\Session\Container(), |
| 54 | + ], true); |
| 55 | +$this->add($csrf); |
| 56 | +``` |
| 57 | + |
| 58 | +where `exampleCsrf` must match the CSRF field's name in the form. |
| 59 | + |
| 60 | +> Don't forget to modify both occurrences in this file. |
| 61 | +
|
| 62 | +> Make sure that you validate the form using its `isValid` method in the handler/controller where it is submitted. |
| 63 | +
|
| 64 | +### Render field |
| 65 | + |
| 66 | +Open the template that renders your form and add the following code somewhere between the form's opening and closing |
| 67 | +tags: |
| 68 | + |
| 69 | +```text |
| 70 | +{{ formElement(form.get('exampleCsrf')) }} |
| 71 | +``` |
| 72 | + |
| 73 | +## Test the implementation |
| 74 | + |
| 75 | +Access your form from the browser and view its source. You should see a new hidden field, called `exampleCsrf` (or |
| 76 | +however you named it). After filling out the form, submitting it should work as before. |
| 77 | + |
| 78 | +In order to make sure that the new CSRF field works as expected, you can inspect the form using your browser's |
| 79 | +`Developer tools` and modify its value in any way. Submitting a filled out form should result in a validation error: |
| 80 | + |
| 81 | +> **CSRF** is required and cannot be empty |
| 82 | +
|
| 83 | +### Timeout |
| 84 | + |
| 85 | +Note the `timeout` option in your PHP form's `exampleCsrf` field, with its default value set to **3600**. This |
| 86 | +represents the value in seconds for how long the token is valid. Submitting a form that has been rendered for longer |
| 87 | +than this value will result in a validation error: |
| 88 | + |
| 89 | +> **CSRF** is invalid |
| 90 | +
|
| 91 | +You can modify the value of `timeout` in each form, but the default value should work in most cases. |
0 commit comments