Symfony CSRF
last modified July 5, 2020
Symfony CSRF tutorial shows how to implement CSRF protection in Symfony applications.
Symfony
Symfony is a set of reusable PHP components and a PHP framework for web projects. Symfony was published as free software in 2005. Symfony was inspired by Django, Spring, and ROR frameworks.
CSRF
Cross-site request forgery (CSRF) is an attack in which malicious users attempt to make legitimate users unknowingly submit data that they do not intend to submit. CSRF attacks specifically target state-changing requests, not theft of data. A successful CSRF attack can force the user to perform state changing requests like transferring funds or changing their profile details.
CSRF protection must be applied for HTTP requests that are considered unsafe. Safe methods do not have to be protected against CSRF because they do not make changes to the application. Check the should-i-use-csrf-protection-for-get-requests for more details.
CSRF protection works by adding a hidden field to the form that contains a value (token) that only the application and the user know. This ensures that the user - not some other entity - is submitting the given data.
The symfony/security-csrf
component provides
CsrfTokenManager
for generating and validating CSRF tokens.
Forms created with the Symfony Form component and form builders include CSRF
tokens by default and Symfony checks them automatically. In such cases, we do
not have to do anything to be protected against CSRF attacks.
If we do not use form component or form builders, we need to handle CSRF ourselves (with Symfony tools).
The csrf_token()
Twig directive renders the CSRF token for a user.
Symfony CSRF protection example
In the following example, we manually create a form for which we implement
the CSRF protection. In this application we define routes in the routes.yaml
file.
$ symfony new mycsrf $ cd mycsrf
With symfony
CLI we create a new Symfony skeleton project and
locate to the project directory.
$ composer req twig symfony/security-csrf
We install the twig
and the security-csrf
packages.
index: path: / controller: App\Controller\AppController::index process-form: path: /process controller: App\Controller\AppController::processForm
We define two routes for the application. The index
route shows
the home page with the form. The process-form
processes the
submitted form and checks the CSRF token.
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class AppController extends AbstractController { public function index(): Response { return $this->render('home/index.html.twig'); } public function processForm(Request $request): Response { $token = $request->request->get("token"); if (!$this->isCsrfTokenValid('myform', $token)) { return new Response('Operation not allowed', Response::HTTP_BAD_REQUEST, ['content-type' => 'text/plain']); } $name = $request->request->get("name"); $email = $request->request->get("email"); $msg = "$name with $email received"; return new Response($msg, Response::HTTP_CREATED, ['content-type' => 'text/plain']); } }
The AppController
has two actions: index()
and processForm()
.
public function index(): Response { return $this->render('home/index.html.twig'); }
The index()
function renders the home page. The home page
contains the HTML form.
$token = $request->request->get("token");
We retrieve the CSRF token with the get()
method from the request.
if (!$this->isCsrfTokenValid('myform', $token)) { return new Response('Operation not allowed', Response::HTTP_BAD_REQUEST, ['content-type' => 'text/plain']); }
We check the validity of the token with isCsrfTokenValid()
method.
If the token is not valid, we return a response with Response::HTTP_BAD_REQUEST
code.
The name of the token myform
is specified in the HTML form in the template.
{% extends 'base.html.twig' %} {% block title %}Home page{% endblock %} {% block body %} <section class="ui container"> <form class="ui form" action="{{ path('process-form') }}" method="post"> <input type="hidden" name="token" value="{{ csrf_token('myform') }}"> <div class="field"> <label>Name:</label> <input name="name" type="text"> </div> <div class="field"> <label>Email</label> <input name="email" type="text"> </div> <button class="ui button" type="submit">Send</button> </form> </section> {% endblock %}
This is the Twig template for the home page with the form. The form is styled with the Semantic UI library.
<form class="ui form" action="{{ path('process-form') }}" method="post">
The form action points to the process-form
path. The form's method is POST,
which means that CSRF protection is necessary.
<input type="hidden" name="token" value="{{ csrf_token('myform') }}" />
We add the hidden input with the CSRF token. The token is generated with
csrf_token()
.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %} <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet"> {% endblock %} </head> <body> {% block body %}{% endblock %} {% block javascripts %}{% endblock %} </body> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.js"></script> </html>
This is the base template file. It loads the Semantic UI library.
$ symfony serve
We run the application.
$ curl -d "name=Peter&email=peter@example.com" -X POST http://localhost:8000/process Operation not allowed
If we try to bypass the form and try to access the controller action with the
curl
tool, we get an error message.
In this tutorial we have implemented the CSRF protection in our Symfony application.
List all Symfony tutorials.