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
{
// ...
}
}
Very handy! Am I wrong in thinking the "tokenKey" argument in the second code block should be "_token" instead of "token", to really be equivalent to the first code block?
@Cédric you are right. I've just updated the blog post to remove the "tokenKey" option because you don't have to define it when the token name is "_token", which is the default name. Thanks!
But how can this be coded for the following projects?
What should I do if the CSRF token is not valid?
As said before by Sébastien JEAN, there is no default handler to an invalid or even a way to implement this method. The better way is to create an interface or an abstract class that we can implement the verification, in case of the _token in the header instead of the payload for example.