|
| 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( |
| 26 | + (new \Laminas\Form\Element\Csrf('exampleCsrf')) |
| 27 | + ->setOptions([ |
| 28 | + 'csrf_options' => ['timeout' => 3600, 'session' => new Container()], |
| 29 | + ]) |
| 30 | + ->setAttribute('required', true) |
| 31 | +); |
| 32 | +``` |
| 33 | + |
| 34 | +where `exampleCsrf` should be a suggestive name that describes the purpose of the field (example: `forgotPasswordCsrf`). |
| 35 | + |
| 36 | +### Validate field |
| 37 | + |
| 38 | +Open the InputFilter that validates the form fields and append the following code to the method that initializes the |
| 39 | +fields (usually `init`): |
| 40 | + |
| 41 | +```php |
| 42 | +$this->add(new \Admin\App\InputFilter\Input\CsrfInput('exampleCsrf')); |
| 43 | +``` |
| 44 | + |
| 45 | +where `exampleCsrf` must match the CSRF field's name in the form. |
| 46 | + |
| 47 | +> Don't forget to modify both occurrences in this file. |
| 48 | +
|
| 49 | +> Make sure that you validate the form using its `isValid` method in the handler/controller where it is submitted. |
| 50 | +
|
| 51 | +### Render field |
| 52 | + |
| 53 | +Open the template that renders your form and add the following code somewhere between the form's opening and closing tags: |
| 54 | + |
| 55 | +```text |
| 56 | +{{ formElement(form.get('exampleCsrf')) }} |
| 57 | +``` |
| 58 | + |
| 59 | +## Test the implementation |
| 60 | + |
| 61 | +Access your form from the browser and view its source. You should see a new hidden field, called `exampleCsrf` (or however you named it). After filling out the form, submitting it should work as before. |
| 62 | + |
| 63 | +In order to make sure that the new CSRF field works as expected, you can inspect the form using your browser's `Developer tools` and modify its value in any way. Submitting a filled out form should result in a validation error: |
| 64 | + |
| 65 | +> This field is required and cannot be empty. |
| 66 | +
|
| 67 | +### Timeout |
| 68 | + |
| 69 | +Note the `timeout` option in your PHP form's `exampleCsrf` field, with its default value set to **3600**. This represents the value in seconds for how long the token is valid. Submitting a form that has been rendered for longer than this value will result in a validation error: |
| 70 | + |
| 71 | +> Invalid CSRF. |
| 72 | +
|
| 73 | +You can modify the value of `timeout` in each form, but the default value should work in most cases. |
0 commit comments