1

Exists some way in Symfony 2 to generate CSRF token at each rendering of form? In my controller I tried something like this:

$request = $this->get('request');
    if ($request->getMethod() != 'POST') {
        $csrf = $this->get('form.csrf_provider');
        $date= new \DateTime("now");
        $this->date = $date->format('Y-m-d H:i:s');
        $token = $csrf->generateCsrfToken($this->date);
    } elseif($request->getMethod() == "POST") {
        $csrf = $this->get('form.csrf_provider');
        $token = $csrf->generateCsrfToken($this->date);
    }

    $form =  $this->createFormBuilder()
    ....
    ->add('_token','hidden',array(
        'data' => $token
        ))->getForm();

    if ($request->getMethod() == 'POST') {
        $form->bindRequest($request);

        if ($form->isValid()) {
            DO something
        }
    }

All the time is in my request right token hash. But after bindRequest its change to default hash generated from security string in parameters.ini and isValid method returns certainly FALSE response. Exists some way, how to adjust it?

EDIT In response to theunraveler answer, I edited my controller and create my CSRF provider, but still im geting "The CSRF token is invalid. Please try to resubmit the form" error. My CSRF provider looks like this:

namespace My\Namespace;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;

class MyCsrfProvider implements CsrfProviderInterface
{
    protected $session;
    protected $secret;
    protected $datetime;

    public function __construct(Session $session, $secret)
    {
        $this->secret = $secret;
        $this->datetime = new \DateTime('now');
        $this->session = $session;
    }

    public function generateCsrfToken($intention)
    {
        return sha1($this->secret.$intention.$this->datetime->format('YmdHis').$this->getSessionId());
    }

    public function isCsrfTokenValid($intention, $token)
    {
        return $token === $this->generateCsrfToken($intention);
    }

    protected function getSessionId()
    {
        return $this->session->getId();
    }
}

Than I add to config.yml service class:

services:
form.csrf_provider: 
    class: My\Namespace\MyCsrfProvider
    arguments: [ @session, %kernel.secret% ]

And Controller I change To this:

//any form without _token field
    $form =  $this->createFormBuilder()
        ->add('field1')
        ->add('field2')->getForm()

        if ($this->getRequest()->getMethod() == 'POST') {

            $request = $this->getRequest();
            $form->bindRequest($request);

            if ($form->isValid()) {
                Do something
                return $this->redirect($this->generateUrl('somewhere'));
            }
        }

Cant be problem in seconds in my hash? Becouse if I remove seconds from datetime format, it works, but with 1 min expiration and its not a good solution. I think, the reason is, that the time was changed.

gavec
  • 205
  • 5
  • 17

2 Answers2

1

Symfony's Form component generates the CSRF token in a Form event. See the class Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener on how the existing implementation works. That means when you call bindRequest() (or bind() in Symfony 2.2+) on your form, that form event gets called and overwrites the token you declared.

The correct way to define a different token is to create a CsrfProvider, which would basically be a class that you write that implements Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface. Check out Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider for an example of how to implement this.

Once you write your class, in your app/config/parameters.yml (or another config file, it doesn't matter where), set the form.csrf_provider.class parameter to the name of your class.

After all of that, you should be able to remove all of the custom code you wrote from your controller, and just use the form system as though nothing were any different (i.e., remove the ->add('_token')... parts).

Hope that helps!

theunraveler
  • 3,254
  • 20
  • 19
  • Thanks for response. I edited my question and added there my provider, but still does not work correctly. – gavec Apr 10 '13 at 10:14
  • 1
    It's probably because having a CSRF token based on the current time can never work, unless you record the initial timestamp somewhere and reuse it when validating the token. For example `generateCsrfToken()` will be different when your form is first built and displayed, and then when the form is submitted and bound. In other words, I think your class "works", but your original intent is flawed. – theunraveler Apr 10 '13 at 14:22
  • @theunraveler I also have the same problem. Unfortunately our client wants to have a CSRF generated every page request. You were talking about saving the timestamp somewhere. I just have a few clarification: 1. Would it be ok to save this in a session variable? 2. Wouldn't it be easy for the hackers to read the "unencoded" timestamp from the session variable? 3. Why not store the entire hashed token instead? – JohnnyQ Jan 28 '15 at 16:56
  • All good I think I found the answer [here](http://stackoverflow.com/questions/20504846/why-is-it-common-to-put-csrf-prevention-tokens-in-cookies) However, I still do appreciate your inputs. TIA – JohnnyQ Jan 28 '15 at 17:02
-1

Are you aware of form_rest? Add this to the bottom of your form to have it automatically generate your CSRF token:

{{ form_rest(form) }}
Sam Selikoff
  • 12,366
  • 13
  • 58
  • 104
  • Of course, im using rest, but there is no problem. The _token hidden field is rendered correctly. But problem is after form submiting, when during the bindRequest method happened something what change my token in form object. It change it to default value, which is setted in parameters.ini as I wrote in question. – gavec Apr 08 '13 at 22:02