8

Edit:

A whole series of new insights gained after asking this question have taught me what the issue was, and it definitely did not have anything to do with the described server migration.

The two given answers show how to "fix" this for both CakePHP 2 and 3, though bear in mind this might pose a security risk. The CSRF component is an important security feature, and should not be disabled lightly.

Original question:

I migrated my CakePHP 3 project from XAMPP on my laptop to XAMPP on a server. Ever since when I activate the Security component, cake throws me an error. Here it is, directly from the Error log:

    2016-05-21 20:32:01 Error: [Cake\Controller\Exception\AuthSecurityException] '_Token' was not found in request data.
Request URL: /Users/addUser
Referer URL: http://localhost/users/add_user
Stack Trace:
#0 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Controller\Component\SecurityComponent.php(324): Cake\Controller\Component\SecurityComponent->_validToken(Object(App\Controller\UsersController))
#1 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Controller\Component\SecurityComponent.php(130): Cake\Controller\Component\SecurityComponent->_validatePost(Object(App\Controller\UsersController))
#2 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Event\EventManager.php(386): Cake\Controller\Component\SecurityComponent->startup(Object(Cake\Event\Event))
#3 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Event\EventManager.php(356): Cake\Event\EventManager->_callListener(Array, Object(Cake\Event\Event))
#4 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Event\EventDispatcherTrait.php(78): Cake\Event\EventManager->dispatch(Object(Cake\Event\Event))
#5 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Controller\Controller.php(495): Cake\Controller\Controller->dispatchEvent('Controller.star...')
#6 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(109): Cake\Controller\Controller->startupProcess()
#7 C:\xampp\htdocs\vendor\cakephp\cakephp\src\Routing\Dispatcher.php(87): Cake\Routing\Dispatcher->_invoke(Object(App\Controller\UsersController))
#8 C:\xampp\htdocs\webroot\index.php(37): Cake\Routing\Dispatcher->dispatch(Object(Cake\Network\Request), Object(Cake\Network\Response))
#9 {main}

I found CakePHP security component blackholing login (data[_Token][key] field not generated), here on StackOverflow, but no other relevant information as to what's causing my problem. In my Appcontroller:

    public function initialize()
    {
        parent::initialize();

        $this->loadComponent('Security');
        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');
Berry M.
  • 469
  • 1
  • 4
  • 17

5 Answers5

5

EDIT after @Invincible comment

Be careful when disabling csrf and security components, they provide protection against csrf and things like form-tampering, forcing ssl, http methods etc https://book.cakephp.org/3.0/en/controllers/components/security.html.

This answer shows only how to disable them, in case if you are sure you do not need them for that request.

Original Answer

In case of ajax requests you can can disable Security Component for that specific action (equivalent to making the action as unlocked in cake 2.x)

put this code in your controller's beforeFilter

$actions = [
    'action1',
    'action2'
];

if (in_array($this->request->params['action'], $actions)) {
    // for csrf
    $this->eventManager()->off($this->Csrf);

    // for security component
    $this->Security->config('unlockedActions', $actions);
}

disabling csrf component http://book.cakephp.org/3.0/en/controllers/components/csrf.html#disabling-the-csrf-component-for-specific-actions

disabling security component http://book.cakephp.org/3.0/en/controllers/components/security.html#disabling-security-component-for-specific-actions

dav
  • 8,931
  • 15
  • 76
  • 140
  • 1
    disabling CSRF checks and security component for the specific action could cause security issues. I think this is not the right solution. – Invincible Aug 23 '18 at 11:25
  • 1
    @Invincible, you are right, I edited the answer to clarify that my point was just to show how to disable it, whenever one is sure what he is doing – dav Aug 23 '18 at 15:26
  • I've accepted @Invincible's newer answer (used to be this one), as it shows the solution quite a bit better. – Berry M. Aug 23 '18 at 17:11
  • 1
    @BerryM. sure mate – dav Aug 23 '18 at 17:23
  • @dav, What if I am creating API? – Kelly Dec 24 '21 at 07:38
  • @Kelly I think here is a good discussion https://security.stackexchange.com/questions/166724/should-i-use-csrf-protection-on-rest-api-endpoints – dav Dec 25 '21 at 11:01
5

The error is related to the _TOKEN. When we create a CakePHP form and then based on the input fields the CakePHP generates hidden field named _TOKEN.

For example:

<?= $this->Form->create(false, [
    'id' => "ajaxForm",
    'url' => [
        'controller' => 'TPCalls', 
        'action' => 'add'
    ],
    'class'=> "addUpdateDeleteEventForm"
    ]); 
?>
<?= $this->Form->input('id', ['label' => false]); ?>
<?= $this->Form->input('start', ['label' => false]); ?>
<?= $this->Form->input('end', ['label' => false]); ?>
<?= $this->Form->input('title', ['label' => false]); ?>
<?= $this->Form->hidden('ADD', ['value' => 'true']); ?>
<?= $this->Form->end(); ?>

Now you should see _TOKEN value in the form when inspecting the HTML:

<input type="hidden" name="_Token[fields]" autocomplete="off" value="---HASH---">

If you do not have any visible fields then _Token will be empty. If you need to have invisible fields then simply add a hidden class on the form or the field.

Anyways, back to the main question. The error is caused by the _TOKEN field's absence. In above case, I would serialize my form before making the Ajax call.

    //serializing the form    
    var ajaxdata = $("#ajaxForm").serializeArray();

    //ajax
    $.ajax({
        url:$("#ajaxForm").attr("action"),
        type:"POST",
        beforeSend: function(xhr){
            xhr.setRequestHeader("X-CSRF-Token", $('[name="_csrfToken"]').val());
        },
        data:ajaxdata,
        dataType: "json",
        success:function(response) {
            console.log(response);
        },
        error: function(response) {
            console.error(response.message, response.title);
        }
    });

Please note, in the ajax, I am using URL from the Cakephp form instead of hard coding it in the ajax. This way, it will be using cakephp url helper.

Invincible
  • 1,418
  • 18
  • 23
  • I also encounter this issue, and after searching in the (Cakephp source code)[https://github.com/cakephp/cakephp/blob/3.7.4/src/Controller/Component/SecurityComponent.php]. I found that your solution is different than my implementation in the following: 1. `xhr.setRequestHeader("X-CSRF-Token", $('[name="_csrfToken"]').val());` is not needed 2.`var ajaxdata = $("#ajaxForm").serializeArray();` might need to be further process like so `var ajaxdata={}; $.each($('#ajaxForm').serializeArray(), function() {ajaxdata[this.name] = this.value;});` – Ng Sek Long Feb 18 '19 at 09:27
5

UPDATE: Also, make sure you didn't forget echo $this->Form->end(); as it adds all the necessary tokens. Original answer below.

UPDATE: You may also run into this issue when submitting a form created separately via new \Cake\View\ViewBuilder()


The correct answer is indeed to invest some time into updating your code to where it's following the security component guidelines. Disabling the component, or unlocking a specific action, is a workaround, and not a solution.

Several not-so-obvious things about _Token.

Prerequisites: I have a placeholder <form> used for building repetitive, almost identical AJAX requests (one field is being constantly updated in a javascript loop and re-submitted; don't ask why).

  • _Token is linked to the form action URL. You can't have your placeholder form point to e.g. javascript:; and have actual requests, signed with it's token, go to some other endpoint

  • _Token is reusable. It is possible to issue multiple requests (i.e. submit the same form over and over again) signed with the same token, it's not a one-time token like I thought

So what I did was put my varying piece of information into a type="text" input and display:none it, instead of using a type="hidden" input, which would fall under the form tampering prevention. Then I serialize()'d the form and put it into the jQuery.ajax() data property.

// update the CSS-hidden type="text" input
document.forms.exampleForm.quantity.value=newValue;
// submit the form
jQuery.ajax({
    url: document.forms.exampleForm.action,
    type: document.forms.exampleForm.method,
    data: jQuery(document.forms.exampleForm).serialize(),
    complete: function(jqXHR) {
        //
    }
});

Surely one could take the easy unlockedActions route but you'll probably be glad if you don't.

ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
  • Ran into this problem again, found my own answer. Another potential cause for the issue is [the PHP's `max_input_vars` limit which could be truncating the `_Token` inputs](https://stackoverflow.com/q/21145441) which happen to come last – ᴍᴇʜᴏᴠ Apr 08 '19 at 07:16
  • Same issue again. `grep -inR "max_input_vars" /etc` and set to 10000. Love finding my own answers. – ᴍᴇʜᴏᴠ Jan 06 '20 at 13:53
0

@Invincible answer is good and all, but applying csrf that way seems like a nightmare to apply and maintenance, since we already have like 20 or so Ajax in our application.

So I use Cakephp 3 - element to help abstract some of the code. I am pasting my code here for other to reference if you also want to abstract the csrf token.

Here's the code:

The element: csrf_ajax_element.ctp

<?= 
    $this->Form->create(false, [ 
        "id" => $name . "Form",
        "url" => $url,
    ]); 
?>

<? if(isset($params)): ?>
    <? foreach($params as $param) : ?>
    <?= $this->Form->input($param, ['label' => false, 'style' => 'display:none;']); ?>
    <? endforeach; ?>
<? endif; ?>
<?= $this->Form->end(); ?>

<script type="text/javascript">
    var csrfName = '<?=$name?>';
    var url = '<?= $this->Url->build($url) ?>';
    var csrf = { };
    $.each($('#'+csrfName+'Form').serializeArray(), function() {csrf[this.name] = this.value;});

    $("#"+csrfName).data('csrf', csrf);
    $("#"+csrfName).data('url', url);
</script>

To add an ajax on a page, do the following:

some_page.ctp

<!-- At the top -->

<input id="myAjaxCsrfToken" type="hidden" data-csrf="" data-url="" />

<?= $this->element('csrf_ajax_element', 
    [
        "name" => "myAjaxCsrfToken", 
        "params" => ['year'],
        "url" => ["controller" => "Api", "action" => "myAjax", "_method" => "POST" ]
    ]) 
?>

<!-- When you need to use the ajax -->
<script type="text/javascript">
$.ajax({
    url: $("#myAjaxCsrfToken").data('url'),
    type: 'POST',
    data: $.extend(
        $("#myAjaxCsrfToken").data('csrf'), 
        { year: 2019 }
    ),
    complete: function() {
        // things
    }
});
</script>

Note: At the above, year is a custom param that need to pass to the ajax along with the token param, if you don't do it cakephp will output security error.

Community
  • 1
  • 1
Ng Sek Long
  • 4,233
  • 2
  • 31
  • 38
-1

I also suffered from the same things, but it was solved

cakephp2.10.2

$this->Security->unlockedActions = array('action1', 'action2');
hase _done
  • 15
  • 1