Preventing Cross-Site Request Forgery (CSRF) in Symfony Applications

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

  1. A user logs into bank.example.com
  2. The session cookie is stored in the browser
  3. The user visits a malicious website
  4. The malicious site submits a hidden form to bank.example.com/transfer
  5. The browser automatically sends the session cookie
  6. 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_manager

Common 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

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.