CSRF in Symfony: A Practical Guide (with Examples)

Updated: January 14, 2024 By: Guest Contributor Post a comment

Introduction

Cross-Site Request Forgery (CSRF) is a type of security exploit where an attacker tricks a user into executing unwanted actions on a web application in which they’re currently authenticated. In this tutorial, we will look at how to handle CSRF protection in Symfony, one of the most popular PHP frameworks.

Symfony provides an easy-to-use CSRF token service out of the box. To understand how it works and how we can leverage it to secure our Symfony applications, we need to systematically go through several aspects of CSRF prevention.

Enabling CSRF Protection

First and foremost, check that CSRF protection is enabled in your Symfony application. By default, Symfony enables CSRF protection for form submissions. This behavior can be managed in the framework.yaml configuration file:

framework:
    csrf_protection:
        enabled: true

When CSRF protection is enabled, Symfony will automatically add a hidden CSRF token field to all forms that are created using the Symfony Form Builder.

Applying CSRF Tokens

To include a CSRF token in a form, make sure you create your form using the Symfony FormBuilder. Here is an example of a basic form with CSRF token:

use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\CsrfTokenType;

$form = $this->createFormBuilder()
    ->add('_csrf_token', CsrfTokenType::class)
    ->getForm();

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
    // ... handle your form submission.
}

This code adds a CSRF token to your form and checks it automatically when the form is submitted.

AJAX and CSRF

If your application makes use of AJAX requests, it’s also important to pass the CSRF token within these requests. In a Symfony controller, you can generate a token like this:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

// ...

class SomeController extends AbstractController
{
    public function apiAction(Request $request, CsrfTokenManagerInterface $csrfTokenManager)
    {
        // Generate CSRF token
        $csrfToken = $csrfTokenManager->getToken('my_action_id')->getValue();

        // Respond with CSRF token
        return new JsonResponse(['csrf_token' => $csrfToken]);
    }
}

The above token (‘my_action_id’) is then submitted along with your AJAX request to validate on the server-side.

Validating CSRF Token Manually

Here’s how you manually validate a CSRF token in a Symfony controller:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

// ...

class SomeController extends AbstractController
{
    public function someAction(Request $request, CsrfTokenManagerInterface $csrfTokenManager)
    {
        $csrfToken = new CsrfToken('my_action_id', $request->request->get('_csrf_token'));

        if (!$csrfTokenManager->isTokenValid($csrfToken)) {
            throw new InvalidCsrfTokenException('Invalid CSRF token.');
        }

        // ... your code for when the CSRF token is valid.
    }
}

This code will throw an exception when a CSRF token is invalid, thus rejecting the request.

Testing CSRF Protection

Testing your CSRF protection is an essential part of development. Symfony provides tools like the CsrfTokenManager to help simulate and validate CSRF behavior during tests:

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

// ...

class SomeControllerTest extends WebTestCase
{
    public function testCsrfProtection()
    {
        $client = static::createClient();
        $csrfTokenManager = $client->getContainer()->get('security.csrf.token_manager');
        $token = $csrfTokenManager->getToken('my_action_id')->getValue();

        $client->request('POST', '/path/to/your/form', [
            '_csrf_token' => $token,
            // other form fields
        ]);

        $this->assertTrue($client->getResponse()->isSuccessful());
    }
}

Finally, in any guide about security, it’s essential to remind readers always to keep up with the latest security advisories and updates from Symfony and the PHP community at large. Security is a constantly shifting landscape, and today’s best practices may be outdated tomorrow. Keep learning, keep coding, and stay secure.

I hope this guide has provided you with a solid grounding in CSRF protection within Symfony, and that you feel more confident implementing these practices in your future projects. Happy coding!