Security is a critical concern in modern web applications, especially when handling authenticated users. One of the most common and dangerous web vulnerabilities is Cross-Site Request Forgery (CSRF).
Symfony provides powerful built-in mechanisms to protect your PHP applications against CSRF attacks. In this article, we will explain what CSRF is, how it works, and how to properly prevent it in Symfony applications.
What Is Cross-Site Request Forgery (CSRF)?
A Cross-Site Request Forgery attack occurs when a malicious website tricks an authenticated user into performing an unwanted action on another website without their consent.
Because browsers automatically include cookies (including session cookies) with every request, the target application may treat the forged request as legitimate.
Example CSRF Attack Scenario
- A user logs into
bank.example.com - The session cookie is stored in the browser
- The user visits a malicious website
- The malicious site submits a hidden form to
bank.example.com/transfer - The browser automatically sends the session cookie
- The bank processes the request as if the user initiated it
Why CSRF Attacks Are Dangerous
CSRF attacks can allow attackers to:
- Change user passwords
- Transfer money
- Modify email addresses
- Delete accounts
- Perform privileged actions without user interaction
Any authenticated action that relies solely on cookies for authorization is potentially vulnerable.
How Symfony Protects Against CSRF Attacks
Symfony uses CSRF tokens to protect against request forgery. A CSRF token is a unique, unpredictable value that must be included with every state-changing request.
Because an attacker cannot read the token value from another origin, they cannot forge a valid request.
Enabling CSRF Protection in Symfony Forms
CSRF protection is enabled by default for Symfony forms. When using the Form component, Symfony automatically:
- Generates a CSRF token
- Embeds it in the form
- Validates it on submission
Example Symfony Form with CSRF Protection
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('save');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_token_id' => 'profile_form',
]);
}
}Symfony automatically renders a hidden input field containing the CSRF token.
CSRF Tokens in Twig Templates
When using Symfony forms in Twig, CSRF tokens are handled automatically:
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}The rendered HTML will include a hidden field similar to:
<input type="hidden" name="_token" value="random_token_value">Manual CSRF Protection (Without Forms)
For APIs, AJAX requests, or manual forms, Symfony provides the CsrfTokenManager.
Generating a CSRF Token
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
public function new(CsrfTokenManagerInterface $csrfTokenManager)
{
$token = $csrfTokenManager->getToken('delete-item')->getValue();
}Validating a CSRF Token
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
public function delete(
Request $request,
CsrfTokenManagerInterface $csrfTokenManager
) {
$token = new CsrfToken(
'delete-item',
$request->request->get('_token')
);
if (!$csrfTokenManager->isTokenValid($token)) {
throw $this->createAccessDeniedException('Invalid CSRF token.');
}
}CSRF Protection for AJAX Requests
When working with JavaScript, CSRF tokens are typically:
- Rendered into the page as a meta tag
- Included in request headers
Expose Token in Twig
<meta name="csrf-token" content="{{ csrf_token('ajax') }}">Send Token via JavaScript
fetch('/api/delete', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content')
}
});CSRF and Symfony Security Firewall
Symfony integrates CSRF protection into the Security component, especially for login forms.
The default login form automatically validates CSRF tokens when configured correctly:
security:
firewalls:
main:
form_login:
csrf_token_generator: security.csrf.token_managerCommon CSRF Mistakes in Symfony Applications
- Disabling CSRF protection for forms without reason
- Trusting HTTP referrer headers
- Using GET requests for destructive actions
- Reusing the same token ID everywhere
- Forgetting CSRF protection in AJAX endpoints
Additional Security Best Practices
- Always use POST, PUT, PATCH, or DELETE for state-changing actions
- Use SameSite cookies to reduce CSRF risk
- Combine CSRF protection with proper authorization checks
- Regenerate session IDs after login
Conclusion
Cross-Site Request Forgery is a serious security threat, but Symfony makes it easy to protect your applications. By consistently using CSRF tokens and following best practices, you can prevent attackers from exploiting authenticated user sessions.
CSRF protection should be considered mandatory for all state-changing operations in Symfony applications.
Here are a great summarisation of vulnerabilities also to monitor on your website