'_Token' was not found in request data in CakePHP3 after server migration
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
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.
UPDATE: Also, make sure you didn't forget echo $this->Form->end();
as it adds all the necessary tokens. Original answer below.
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.