1

tests/TestCase/Controller/FeedbackControllerTest.php:45

public function testAdd()
{
    $this->enableCsrfToken();
    $this->enableSecurityToken();
    $this->session([
        'Auth' => [
            'User' => [
                'id' => 1,
                'role' => 'REPR',
            ]
        ]
    ]);
    $this->configRequest([
        'headers' => ['Accept' => 'application/json']
    ]);
    $_data = [
        'crash' => 1,
        'details' => 'Lorem ipsum dolor sit amet'
    ];
    $_data = json_encode($_data, JSON_PRETTY_PRINT);
    $this->post('/feedback/add', $_data); // <---- 45
    $expected = [
        'status' => 'success'
    ];
    $expected = json_encode($expected, JSON_PRETTY_PRINT);
    $this->assertEquals($expected, (string)$this->_response->getBody());
}

PHPUnit output:

1) App\Test\TestCase\Controller\FeedbackControllerTest::testAdd
Cake\Http\Exception\InvalidCsrfTokenException: Missing CSRF token cookie

/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:196
/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:120
/vagrant/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:106
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:51
/vagrant/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:168
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:88
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:96
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:65
/vagrant/vendor/cakephp/cakephp/src/Http/Runner.php:51
/vagrant/vendor/cakephp/cakephp/src/Http/Server.php:98
/vagrant/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:201
/vagrant/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:516
/vagrant/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:413
/vagrant/tests/TestCase/Controller/FeedbackControllerTest.php:45

I Have read and try solutions from answers:

How to create CSRF token for Cakephp 3 PHPunit testing?

if i add like @ndm says:

$token = 'my-csrf-token';

$this->cookie('csrfToken', $token);

$data = [
    'email' => 'info@example.com',
    'password' => 'secret',
    '_csrfToken' => $token
];

Then:

Cake\Http\Exception\InvalidCsrfTokenException: CSRF token mismatch.

How to fix ?

Salines
  • 5,674
  • 3
  • 25
  • 50
  • Manually setting the cookie shouldn't be neccesary anymore every since the `enableCsrfToken()` method has been introduced. Try removing bits from your test code, like request config, security token, session config, etc and see if it makes any difference, maybe there's some kind of incompatibility. Also make sure that you haven't changed the middleware's default cookie name config. – ndm May 16 '19 at 14:29
  • Oh wait, you're posting a JSON string... IIRC you still have to do it manually in that case, but you'd have to pass the token as a header (`X-CSRF-Token`) instead of in the post data (can't test it right now). – ndm May 16 '19 at 14:49
  • @ndm how to pass valid token as X-CSRF-Token? – Salines May 17 '19 at 08:20
  • You pass it as a header just like you're already doing with the `Accept` header. – ndm May 17 '19 at 08:51
  • I've just a had look at it, and you definitely need to send it as a header when passing a JSON string. – ndm May 17 '19 at 11:23

2 Answers2

1

When passing a string as POST data, the intergration test case won't automatically set tokens, neither the CSRF token, nor the security token, as it cannot inject anything into the string without knowing about the data format. Consequently it will also not set the cookie.

So in cases where you pass string data, you have to set the cookie and the token manually, similarily as to described in the answer that you've linked. However when using anything other than application/x-www-form-urlencoded data (ie data that PHP will decode and put in the $_POST superglobal), in your example JSON data, you have to pass the token as a header, because the JSON input data will be decoded by the request handler component (there are plans to move this into the middleware layer IIRC), which runs after the CSRF middleware, which consequently won't see any post data.

Example:

$token = 'my-csrf-token';
$this->cookie('csrfToken', $token);

$this->configRequest([
    'headers' => [
        'X-CSRF-Token' => $token,
        // ...
    ]
]);

Security component tokens on the other hand would have to go in the POST data, the security component won't look for headers, and it will have access to the decoded data after the request handler component has run (make sure that you load the request handler component before the security component!). You can refer to the \Cake\TestSuite\IntegrationTestTrait::_addTokens() source to figure how security tokens are built, you'd do it somewhat like this:

$url = '/feedback/add';

$_data = [
    'crash' => 1,
    'details' => 'Lorem ipsum dolor sit amet'
];

$keys = array_map(
    function ($field) {
        return preg_replace('/(\.\d+)+$/', '', $field);
    },
    array_keys(Hash::flatten($_data))
);

$tokenData = $this->_buildFieldToken($url, array_unique($keys));

$_data['_Token'] = $tokenData;
$_data['_Token']['debug'] = 'SecurityComponent debug data would be added here';

Note that the URL that is passed to _buildFieldToken() would also have to include possible query string data!

ndm
  • 59,784
  • 9
  • 71
  • 110
-1

can you please try this code inside your jquery ajax function,

beforeSend: function (xhr) 
{
    xhr.setRequestHeader('X-CSRF-Token', $('[name="_csrfToken"]').val());
},

put the code before success function