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')
);
0

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

7 Comments

How know what is the $intention parameter used in forms ?
you can use any $intention you want, just make sure that its the same one you also use for checking the token
In Symfony 3, the service returned by ->get('form.csrf_provider') is deprecated. Use ->get('security.csrf.token_manager') instead.
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.
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.
|
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.

1 Comment

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.
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
    ]
  ]
);

1 Comment

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 $tokenId
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.

Comments

0

You can create your own token, put in session with login content and it's fine. I put comment in code to explain it.

Here is example code:

// Main method that put everything into client's object
public function fillCsrfToken(KernelBrowser $client, string $tokenId): string {
    $generated = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
    $this->seedSession($client, $this->getCsrfTokenSeed($tokenId, $generated));
    return $generated;
}

// Get all seed informations that will be put in session:
// - The CSRF token
// - The login data
private function getCsrfTokenSeed(string $tokenId, string $token): array {
    return [SessionTokenStorage::SESSION_NAMESPACE."/{$tokenId}" => $token,
        '_security_main' => serialize(new PostAuthenticationToken(new LoginUser("MyUser"), "main", ["ROLE_USER"]))
    ];
}

// Fill client with all sessions informations
private function seedSession(KernelBrowser $client, array $data): void {
    $sessionFactory = $client->getContainer()->get('session.factory');
    \assert($sessionFactory instanceof SessionFactoryInterface);
    $session = $sessionFactory->createSession();
    $session->replace($data);
    $session->save();
    $client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
}

Then I use like this:

$client = self::getLoggedClient(); // create session object
$token = $this->fillCsrfToken($client, "tester"); // fill token and get the created one
$client->request('POST', "/test", [ // make the request
    "_csrf_token" => $token // with the token
]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.