3
votes

So, I have a table that is auto-generated using DataTables. An action in my CakePHP grabs the data for that table, and formats it into JSON for datatables to use, this is the formatted JSON:

<?php
$data = array();
if (!empty($results)) {
    foreach ($results as $result) {
        $data[] = [
          'name' => $result->name,
          'cad' => $this->Number->currency($result->CAD, 'USD'),
          'usd' => $this->Number->currency($result->USD, 'USD'),
          'edit' => '<a href="' .
            $this->Url->build(['controller' => 'Portfolios', 'action' => 'edit', $result->id]) .
    '"><i class="fa fa-pencil"></i></a>',
          'delete' => '<input type="checkbox" class="delete" value="' . $result->id . '">'
        ];
    }
}

echo json_encode(compact('data'));

As you can see, I have a 'delete' option in there that outputs a checkbox with the value of the id of the corresponding element. When that checkbox is checked, a delete button is showing which sends this ajax request:

$('a#delete').on('click', function(e) {
    e.preventDefault();
    var checkedValues = [];
    $('input.delete:checked').each(function() {
        checkedValues.push($(this).val());
    });
    $.ajax({
        url: $(this).attr('href'),
        type: 'POST',
        data: checkedValues
    });
})

This ajax post goes to my controller action delete(). The problem I'm having is that I'm getting an error that states "Invalid Csrf Token". I know why this is happening, I'm submitting a form with Csrf protection on, that has no Csrf token added to it.

I can't figure out how to manually create a Csrf token for this situation (where the input values are generated after the page has loaded). Nor can I figure out how to disable Csrf protection. I read this, but the code is placed in the beforeFilter function, and as far as I understand it, that means it's run on every action, not just this one, and that's not what I want. Plus, to be completely honest, I would prefer a solution where I don't deactivate security functions.

Is there anyway to disable Csrf for this specific action, or is there a better way to do this?

4

4 Answers

11
votes

read all about the CSRF component here

http://book.cakephp.org/3.0/en/controllers/components/csrf.html

you can disable for a specific action here:

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

 public function beforeFilter(Event $event) {
     if (in_array($this->request->action, ['actions_you want to disable'])) {
         $this->eventManager()->off($this->Csrf);
     }
 }
4
votes

Above answer does not work in Cakephp 3.6 or later.

Cakephp add object of CsrfProtectionMiddleware in src/Application.php. If you have to remove CSRF protection for specific controller or action then you can use following work around:

public function middleware($middlewareQueue)
{
    $middlewareQueue = $middlewareQueue
        // Catch any exceptions in the lower layers,
        // and make an error page/response
        ->add(ErrorHandlerMiddleware::class)

        // Handle plugin/theme assets like CakePHP normally does.
        ->add(AssetMiddleware::class)

        // Add routing middleware.
        // Routes collection cache enabled by default, to disable route caching
        // pass null as cacheConfig, example: `new RoutingMiddleware($this)`
        // you might want to disable this cache in case your routing is extremely simple
        ->add(new RoutingMiddleware($this, '_cake_routes_'));
        /*
        // Add csrf middleware.
        $middlewareQueue->add(new CsrfProtectionMiddleware([
            'httpOnly' => true
        ]));
        */
    //CSRF has been removed for AbcQutes controller
    if(strpos($_SERVER['REQUEST_URI'], 'abc-quotes')===false){
        $middlewareQueue->add(new CsrfProtectionMiddleware([
            'httpOnly' => true
        ]));
    }
    return $middlewareQueue;
}
3
votes

So i needed a fix for cakephp 3.7 and using $_SERVER['REQUEST_URI'] is realllly not the way to go here. So here is how you are supposed to do it after reading through some documentation.

In src/Application.php add this function

public function routes($routes)
{
    $options = ['httpOnly' => true];
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
    parent::routes($routes);
}

Comment out the existing CsrfProtectionMiddleware

public function middleware($middlewareQueue)
{ 
  ...
  //            $middlewareQueue->add(new CsrfProtectionMiddleware([
  //                'httpOnly' => true
  //            ]));
}

Open your config/routes.php add $routes->applyMiddleware('csrf'); where you do want it

Router::prefix('api', function ($routes)
{
  $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
  $routes->fallbacks(DashedRoute::class);
});

Router::scope('/', function (RouteBuilder $routes)
{
  $routes->applyMiddleware('csrf');
  $routes->connect('/', ['controller' => 'Pages', 'action' => 'dashboard']);
  $routes->fallbacks(DashedRoute::class);
});

Note that my api user now has no csrf protection while the basic calls do have it. If you have more prefixes don't forgot to add the function there aswell.

0
votes

in Application.php this worked for me....

    $csrf = new CsrfProtectionMiddleware();
    
    // Token check will be skipped when callback returns `true`.
    $csrf->whitelistCallback(function ($request) {
    // Skip token check for API URLs.
      if ($request->getParam('controller') === 'Api') {
          return true;
      } 

    });