Yassine Guedidi
Contributed by Yassine Guedidi in #52961 and #54443

CSRF (Cross-site Request Forgery) attacks are used by malicious users to make your legitimate users submit data unknowingly. In Symfony we provide full protection against CSRF attacks thanks to the SecurityCsrf component.

The following is a common code snippet used in controllers that extend Symfony's AbstractController to check that the CSRF token of the form is valid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class BlogPostController extends AbstractController
{
    // ...

    public function delete(): Response
    {
        if (!$this->isCsrfTokenValid('delete_example', $request->request->getString('_token'))) {
            throw new BadRequestHttpException('This token is invalid');
        }

        // ...
    }
}

In Symfony 7.1 we're introducing a new #[IsCsrfTokenValid] attribute to make this code simpler:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;

class BlogPostController
{
    // ...

    #[IsCsrfTokenValid('delete_example')]
    public function delete(): Response
    {
        // ...
    }
}

Another common need is to check multiple similar CSRF tokens. For example, in a listing of blog posts, each one has a slightly different delete form:

1
2
3
4
5
6
7
{% for post in blog_posts %}
    {# ... #}

    <form action="{{ path('post_delete', {post: post.id}) }}" method="POST">
        <input type="hidden" name="_token" value="{{ csrf_token('delete-post-' ~ post.id) }}">
    </form>
{% endfor %}

This can also be solved with the #[IsCsrfTokenValid] attribute because it supports expressions compatible with the ExpressionLanguage component:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
use Symfony\Component\ExpressionLanguage\Expression;

class BlogPostController
{
    // ...

    #[IsCsrfTokenValid(new Expression('"delete-post-" ~ args["post"].id'))]
    public function delete(Request $request, Post $post): Response
    {
        // ...
    }
}
Published in #Living on the edge