Get the CSRF token in test
Asked Answered
M

4

18

I'm writing functional test and i need to make ajax post request. "The CSRF token is invalid. Please try to resubmit the form". How can i get the token in my functional test ?

$crawler = $this->client->request(
  'POST', 
  $url, 
  array(
      'element_add' => array(
          '_token' => '????',
          'name'   => 'bla',
      )

  ), 
  array(), 
  array('HTTP_X-Requested-With' => 'XMLHttpRequest')
);
Myopic answered 1/3, 2012 at 21:9 Comment(0)
A
22

CSRF token generator is normal symfony 2 service. You can get service and generate token yourself. For example:

    $csrfToken = $client->getContainer()->get('form.csrf_provider')->generateCsrfToken('registration');
    $crawler = $client->request('POST', '/ajax/register', array(
        'fos_user_registration_form' => array(
            '_token' => $csrfToken,
            'username' => 'samplelogin',
            'email' => '[email protected]',
            'plainPassword' => array(
                'first' => 'somepass',
                'second' => 'somepass',
            ),
            'name' => 'sampleuser',
            'type' => 'DSWP',
        ),
    ));

The generateCsrfToken gets one important parameter intention which should be the same in the test and in the form otherwise it fails.

Answerable answered 6/3, 2013 at 8:10 Comment(7)
How know what is the $intention parameter used in forms ?Myopic
you can use any $intention you want, just make sure that its the same one you also use for checking the tokenCavesson
In Symfony 3, the service returned by ->get('form.csrf_provider') is deprecated. Use ->get('security.csrf.token_manager') instead.Clary
The method would be ->get('security.csrf.token_manager')->getToken($intention). If you do any authentication of the user in the test, make sure to generate the token first to avoid the mocked session storage attempting to set the session ID after the session has started.Clary
If you're not sure of the intention, then it's probably 'form'. But if it's not set anywhere obvious (Symfony magic is setting it) then you can temporarily edit vendor/symfony/symfony/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php to throw an exception with the $tokenId parameter, in the getToken() method and, unless your page is multi-formed or otherwise complicated, the exception should tell you the $intention parameter.Gilson
@Clary how can I generate it before authentication if the client is created after autentication with $this->makeClient($credentials)?Hurlbut
@Hurlbut something like this: $token = new UsernamePasswordToken($user, $user->getPassword(), 'member_area', $user->getRoles()); self::$kernel->getContainer()->get('security.token_storage')->setToken($token);Clary
M
14

After a long search (i've found nothing in doc and on the net about how to retrieve csrf token) i found a way:

$extract = $this->crawler->filter('input[name="element_add[_token]"]')
  ->extract(array('value'));
$csrf_token = $extract[0];

Extract the token from response before make the request.

Myopic answered 2/3, 2012 at 10:31 Comment(1)
This works, but it depends on the purpose of the test: if it's a behavioural test, replicating browser behaviour, then it definitely is the way to go (it's what a browser kind of does anyway); but if it's an integration test, checking the controller is integrating properly with the CSRF framework, then it's good to go via the token manager if you can.Gilson
G
8

In symfony 3, in your WebTestCase, you need to get the CSRF token:

$csrfToken = $client->getContainer()->get('security.csrf.token_manager')->getToken($csrfTokenId);

To get the $csrfTokenId, the best way would be to force it in the options of your FormType ():

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'csrf_token_id'   => 'task_item',
        ));
    }

    // ...
}

So in this case: $csrfTokenId = "task_item";. Or you you can try to use the default value, that would be the name of your form.

Then use it as a post parameter:

$client->request(
  'POST',
  '/url',
  [
    'formName' => [
      'field' => 'value',
      'field2' => 'value2',
      '_token' => $csrfToken
    ]
  ]
);
Gunshot answered 29/7, 2016 at 14:29 Comment(1)
This is contemporary answer for symfony3 and should be more actively upvoted. It combines info from accepted answer, its comments and completes with documented way of setting $tokenIdEgesta
W
1

Just in case someone stumble on this, in symfony 5 you get the token this way:

$client->getContainer()->get('security.csrf.token_manager')->getToken('token-id')->getValue();

where 'token-id' is the id that you used in the configureOptions method in your form type, which would look something like this:

public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
           
            "data_class"         => Foo::class,
            "csrf_protection"    => true,
            "csrf_field_name"    => "field_name", //form field name where token will be placed. If left empty, this will default to _token
            "csrf_token_id"      => "token-id", //This is the token id you must use to get the token value in your test
        ]);
    }

Then you just put the token in the request as a normal field.

Wulf answered 10/11, 2020 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.