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.
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');
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With