Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CakePHP ajax post keeps returning 400 Bad Request

I am tring to use an ajax post to an action. GET requests work fine but when I try to POST I see a '400 Bad Request' in firebug and the view return a 'Black hole' response.

Here is the Jquery request:

            $.ajax({
            url:"/usermgmt/users/editUser",
            type:"POST",
            success:function(data) {
                alert('Wow this actually worked');
                //ko.applyBindings(self);

            },
            error:function() {
                alert('This will never work');
            }
        });

Is this due to the security settings of Cake or what am I missing here?

like image 536
kSeudo Avatar asked May 16 '12 18:05

kSeudo


2 Answers

Protection against form tampering is one of the basic features provided by the Security Component. As long as it is enabled, it is going to treat all POSTs as form submissions.

A regular hand-coded HTML form won't work with the Security Component enabled, so neither will a JQuery-generated POST. You can, of course, use $this->Security->validatePost = false; or $this->Security->csrfCheck = false; but then you loose the protection that the Security Component provides.

To keep the Security Component on and working as normal, you need to use the CakePHP Form Helper to create the form you're going to post via ajax. This way the data[_Token][fields] and data[_Token][unlocked] hidden fields get generated with their keys:

<?php 
    echo $this->Form->create('Test',array('id'=>'testform'));
    echo $this->Form->input('Something');
    echo $this->Form->submit();
    echo $this->Form->end();
?> 

This will generate something like this:

<form action="/your/url" id="testform" method="post" accept-charset="utf-8">
    <div style="display:none;">
        <input type="hidden" name="_method" value="POST"/>
        <input type="hidden" name="data[_Token][key]" value="9704aa0281d8b5a2fcf628e9fe6f6c8410d8f07a" id="Token937294161"/>
    </div>
    <div class="input text">
        <input name="data[Test][Something]" class="required" type="text" id="TestSomething"/>
    </div>
    <div class="submit">
        <input  type="submit" />
    </div>
    <div style="display:none;">
        <input type="hidden" name="data[_Token][fields]" value="0c81fda1883cf8f8b8ab39eb15d355eabcfee7a9%3A" id="TokenFields817327064"/>
        <input type="hidden" name="data[_Token][unlocked]" value="" id="TokenUnlocked281911782"/>
    </div>
</form>   

Now it's just a matter of serializing this form in JQuery so that it can be sent with the ajax POST:

    $('#testform').submit(function(event) {
        $.ajax({
            type: 'POST',
            url: "/your/url",
            data: $('#testform').serialize(),
            success: function(data){ 
                alert('Wow this actually worked');
            },
            error:function() {
                alert('This will never work');
            }
        });
        event.preventDefault(); // Stops form being submitted in traditional way
    });

Now if you press the submit button, the POST will succeed.

IMPORTANT: Due to the fact that the Form Helper's Tokens can only be used with the Security Component once, this solution only works if you only intend to POST once per page generation. If you need to be able to post the same form several times between page reloads then you'll need to do the following when you add the Security Component at the beginning of your Controller:

public $components = array(
    'Security' => array(
        'csrfUseOnce' => false
    )
);

...this will allow the tokens to be used for more than one request. It's not as secure but you can combine it with csrfExpires so that the tokens will expire eventually. This is all documented in the CSRF configuration section of the Cake book.

like image 172
Joseph Avatar answered Sep 22 '22 14:09

Joseph


FYI CakePHP 2.3 and above now includes unlockedActions just for this purpose to be used in beforeFilter in your controller, or AppController.

$this->Security->unlockedActions = array('ajaxAction');

From: http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html#disabling-security-component-for-specific-actions

like image 21
Chris Avatar answered Sep 22 '22 14:09

Chris