1

The default CSRF prevention in Symfony is form-based (which happens automatically if you use the provided form builder). However, for AJAX requests it is tedious to manually attach the CSRF token to each HTTP request and then check it manually for each request.

A good approach would be to embed the token as an HTTP header, as suggested in the comments of this question. jQuery can then be configured to include this header on each request as described here.

My question is how best to handle this within Symfony? I can include the CSRF token in each page using Twig, but how do I check incoming requests to ensure that each request contains a valid token in the header?

I'm aware of how to access the HTTP headers using $request->headers->get('X-CSRF-Token') from within a controller, but that still means having to perform this check within each controller individually. Whereabouts should I add this check so that it is caught as early as possible?

Community
  • 1
  • 1
Richard Keller
  • 1,980
  • 2
  • 19
  • 32
  • 1
    You could use the kernel.request event to check before a response is processed. See http://symfony.com/doc/current/book/internals.html#kernel-request-event – Ghassan Idriss Mar 08 '14 at 00:58
  • Ghassen, yes that sounds pretty much like what I am trying to accomplish. Where in Symfony would I include the code to handle the kernel.request event so that I can slip in the check for the presence of the header, and then continue processing the response as normal? – Richard Keller Mar 08 '14 at 06:16
  • 1
    Im out and about tonight but if you are patient i will give you a full example tomorrow, in the meantime you can just google the symfony2 event dispatcher component to get a better idea of how to implement event hooks – Ghassan Idriss Mar 08 '14 at 06:25

1 Answers1

1

Im not 100% sure on this but I would use a BaseFormType and extend that.

Your base form type will use the form event PRE_SET_DATA listener and take the request look for the header then populate that header into the _token field.

//FormType
class BaseFormType extends AbstractType
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $request = $this->request;
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use($request){
            $token = $request->headers->get('X-CSRF-Token');
            if($token){
                $form = $event->getForm();
                $form->get('_token')->setData($token);
            }
        }
    }
}

Then all of your FormTypes will extend this:

//YourCustomFormType
class YourCustomFormType extends BaseFormType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm();
        $form->add('name');
    }
}

Then your controller action

//Controller
public function sayMyNameAction(Request $request)
{
    $name = new Name();
    $form = $this->createForm(new YourCustomFormType($request),$name);
    if($request->isMethod('POST'))
    {
        $form->handleRequest($request);
        if($form->isValid()){
            return new JsonResponse(array('say':$name->getName()));
        }
    }
}

Or something along those lines.

Chase
  • 9,289
  • 5
  • 51
  • 77