AJAX blocked by SecurityComponent


#1

Hello !

I’ve got problems setting up ajax request.
The fact is that everytime I want to get data from a controller’s method, a “invalid token” error is raised.
I tried everything I’ve found on the net like blackhole solution for specific method or even remove SecurityComponent for the complete controller but nothing change :confused:
I’m sure that my ajax is correct so, what can I do?

Thanks !

Ps : For specifics reasons, my form is not construct with CakePhp helper (only with Form->begin and Form->end)


#2

In the beforeFilter you need to allow it through using $this->Auth->allow([…actions]);

If Security is on for the form component you, and you want it to stay on, you’ll need to create the form tokens one way or the other. I’d turn it off and validate/filter your request variables on your own.


#3

I’ll try this evening with Auth->allow but I’m pretty sure that I’ve already try it before …
Just to be sure, the action I need to allow is the one called in the ajax get or/and the one that render my view and my form?

Thank you for your response btw !


#4

Hey there, it can also be a CORS related problem.

Is the javascript submitting the form hosted by the same server that hosts your PHP server? If not you have to specifically allow requests from the domain that you’re trying to submit the form from.


#5

@sathomsen Currently, I’m in dev so I use cake built-in server, I guess it should not be a problem right?

@xaphalanx I tried your solution but ajax still send me "Error: ‘_Token’ was not found in request data. " so I’m still lost :frowning:
Any other idea? (It’s a school project, I need to know exactly why I get this error, even if I solve it…)

Just to be sure, this is my ajax :

$(".clean").click(function(){
    var del_id = $(this).data('id');
    var chart = $('easy-pie-chart-' + $(this).data('chart'));

    $.ajax({
        type:'POST',
        url:"<?= $this->Url->build(['controller'=>'director','action'=>'cleanContainer'])?>",
        data: 'container_id='+del_id,
        beforeSend: function(xhr) {
            xhr.setRequestHeader('X-CSRF-Token', "<?= $this->request->getParams['_csrfToken'] ?>");
        },
        success:function(data) {
            if(data.error != ''){
                alert(data.error);
            }
            if(data == "YES") {
                chart.update(0);
            }
            else {
                showNotification('Probleme while clean containers, try later please.');
            }
        },
        error: function(xhr, status, error) {
            alert(xhr.responseText);
        }
    });
}); 

And my controller :

class DirectorController extends AppController {
    public function initialize() {
        parent::initialize();
        //need to be here for others actions
        $this->loadComponent('Security');
    }

    public function beforeFilter(Event $event){
        $this->Auth->allow(['cleanContainer']);
    }
    
    // definition is not usefull here
    public function status() { ... }

    //Ajax method
    public function cleanContainer($container_id){
        $table = TableRegistry::get('Containers');
        $container = $table->get($container_id);
        $container->current_volume = 0;
        $table->save($container);
    }

#6

The security token is being checked when you hit the server. It is checking to see if your form changed – form tampering. You need to turn off security.

Comment out the loadComponent(‘Security’). Try it again.

Sanitize the your form post yourself.


#7

Check your application controller initialize method?


#8

I can’t disable SecurityComponent for all the controller, I need to keep some security for the other actions… only ajax methods needs to disable it :confused:
First, I try this in the beforeFilter methods : “$this->eventManager()->off($this->Csrf);” for ajax action, no result.
Then : “$this->Security->setConfig(‘unlockedActions’, [‘cleanContainer’, ‘status’]);”, still no result…

And I’ve retry without the component in this controller but this changes nothing.

Finally, after disable loading of Security and CSRF components in the appController, it works!

So I’ll play with loading/dismiss those components to my needs.

Thank you very much for your help !


#9

security still requires these tokens when you post.

the solution recreate a token before post.

in you’re view,
after you’re form->create

<?php echo $this->Form->input('typeaction', array('label' => false, 'div' => false)); ?>

and

<div id="tosubmitform" style="display:none"></div>

and you’re ajax function:

$("#MyIndexTypeaction").val(myurltopost);

    	$.ajax
	({
		url: "<?php echo $this->Html->url(array('action' => 'ajax_getFormToken', 'mymodelname')); ?>" ,
		async: false,
		type: 'POST',
		data: $('#MyIndexForm').serialize(),
		success:function(data)
		{
                    $("#tosubmitform").html(data);
                    $("#ajax_getFormTokenForm").submit();
                    $("#tosubmitform").html('');
		}
	});

in appController.php

    function ajax_getFormToken($modelClass){
    if ( ! $this->request->is('post')) {
        throw new UnauthorizedException();
    }

    $url = str_replace(Router::fullBaseUrl(), '', $this->request->data[$modelClass]['typeaction']);

    if ( $this->request->webroot != '/' ) {
        $url = str_replace($this->request->webroot, '', $url);
    }

    $url = Router::parse($url);
    if ( count( $url['pass'])) {
        $url += $url['pass'];
        unset($url['pass']);
    }
    unset($this->request->data[$modelClass]['typeaction']);

    $data = Hash::flatten($this->request->data);
    $fields = array_keys($data);

    $this->set(compact('modelClass', 'fields', 'url'));
    $this->render('/Elements/formtoken', 'ajax');
}

in formtoken.ctp

	<?php echo $this->Form->create(false, array(
                                'url' => $url,
                                'inputDefaults' => array(
                                    'legend' => false,
                                    'label' => false,
                                    'div' => false,
                                    'default' => false,
                                    'id' => 'ajax_getFormTokenForm'
                                ))
                        );
echo $this->Form->inputs($fields);
echo $this->Form->end(); ?>

Ajax POST requests and SecurityComponent
#10

I was facing the same problem. Here’s how I solved it: in AppController::initialize() method where you have parent::initialize();, find the Security and the Csrf Components and put them in a conditional block like so:

If (!$this->request->is('ajax')) {
    $this->loadComponent('Security');
    $this->loadComponent('Csrf');
}


This will automatically disable the two components for Ajax requests.


#11

Is there any solution?


#13

I use jquery cookie plugin to read the CSRF cookie that is stored in your browser and encapsulate that in a variable in my beforeSend function…

beforeSend: function(request){
                    var csrf = $.cookie('csrfToken');
                    request.setRequestHeader('X-CSRF-Token', csrf);
                }
url:your_url;

After that I send the info to my url un my ajax request and you are done.

Right after in the same ajax call, on your success object you’ll read the response from your controller. That response will be easier to read using JSON.

success:function(data){
    var yourResponse = $.parseJSON(data);
}

and voila, that is in your script, now in your controller you can try this…

1 catch your parameters if any:

public function yourAjaxFriend( $data = null){
    if($this->request->is('ajax','post')){
        $this->response->disableCache();
        $this->autoRender = false;
        $this->RequestHandler->config('inputTypeMap.json', ['json_decode', true]);

       // do all your logic here and set your variables to bring back to the view
       // for example
       $varBack = my_result;
       $this->set(compact($varBack));
       echo json_encode(compact('varBack'));
       exit();
    }
}

Remember to load the cookie before your run your javascript code.

Hope this helps and happy coding !