0

I'm trying to get an ajax request working in CakePHP4, but keep running into the CSRF protection.

In my controller:

 public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);

        $this->Security->setConfig('unlockedActions', ['players']);
    }

public function players()
    {
        $players = ['Sjaak Afhaak', 'Aad Kippezaad', 'Gras Kaffer', 'Tedje van Es'];

        if ($this->request->is('ajax')) {
            $this->response = $this->response->withDisabledCache();
        }

        $letter = trim($this->request->getData('letter'));
        if (!empty($letter)) {
            $players = array_filter($spelers, function ($haystack) use ($letter) {
                return(strpos($haystack, $letter));
            });
        }

        $this->set(compact('players'));
        $this->viewBuilder()->setOption('serialize', ['players']);

        $this->RequestHandler->renderAs($this, 'json');
    }

Then in the template file:

<?php echo $this->Html->script('jquery.min'); ?>

<div class="container">
    <div class="row">
        <div class="column">
            <div class="users form content">
                <?php echo $this->Form->create(null, ['url' => \Cake\Routing\Router::pathUrl('Ado::players')]); ?>
                <fieldset>
                    <legend>Players</legend>
                    <?php echo $this->Form->text('letter', ['placeholder' => 'Begin letter(s)']); ?>
                </fieldset>
                <?= $this->Form->button('OK'); ?>
                <?= $this->Form->end() ?>
            </div>
        </div>
        <div class="column response">
            ...
        </div>
    </div>
</div>

<script>
    $(document).ready(function () {
        $(document).on("submit", "form", function (event) {
            var $form = $(this);
            var $target = $('div.response');
            var csrf = $('input[name=_csrfToken]', $form).val();
            var data = { letter:  $('input[name=letter]', $form).val() };

            $target.html('');

            $.ajax({
                method: "POST",
                url: $form.attr('action'),
                beforeSend: function(xhr) {
                    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                    xhr.setRequestHeader('X-CSRF-Token', csrf);
                },
                data: data,
                dataType: "json"
            })
                .done(function (response) {
                    var items = [];
                    $.each( response, function( key, val ) {
                        items.push( "<li id='" + key + "'>" + val + "</li>" );
                    });

                    $( "<ul/>", {
                        "class": "spelers-list",
                        html: items.join( "" )
                    }).appendTo( $target );
                });

            event.preventDefault();
        });
    });
</script>

Without the beforeSend in the ajax call, i get a 403 response. If i include the X-CSRF-Token i receive a 400 response.

2020-06-18 09:49:38 Error: [Cake\Http\Exception\BadRequestException] `_Token` was not found in request data. in src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php on line 143
Stack Trace:
- src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php:97
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:309
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:286
- src\vendor\cakephp\cakephp\src\Event\EventDispatcherTrait.php:92
- src\vendor\cakephp\cakephp\src\Controller\Controller.php:569
- src\vendor\cakephp\cakephp\src\Controller\ControllerFactory.php:72
- src\vendor\cakephp\cakephp\src\Http\BaseApplication.php:229
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\BodyParserMiddleware.php:164
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authorization\src\Middleware\AuthorizationMiddleware.php:129
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authentication\src\Middleware\AuthenticationMiddleware.php:124
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\CsrfProtectionMiddleware.php:138
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php:166
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php:68
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php:119
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php:60
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Http\Server.php:90
- src\webroot\index.php:41

Not sure if it's related, but i'm using the Authentication plugin. (https://book.cakephp.org/authentication/2/en/index.html)

ndm
  • 59,784
  • 9
  • 71
  • 110
PaulH
  • 52
  • 7
  • Please empty your your app's error/debug logs, issue a request that fails, and then check the logs to figure what errors exactly are occurring, and include the log contents in your question. – ndm Jun 17 '20 at 14:17
  • Added the error log. FormProtectionComponent seems to throw a BadRequestException. Not sure why, because i used ```$this->Security->setConfig('unlockedActions', ['players']);``` in the beforeFilter? – PaulH Jun 18 '20 at 08:00

1 Answers1

2

As can be seen in the stacktrace, the error stems from the form protection component, not the security component, so unlocking actions on the security component won't do anything.

The security component is deprecated (the Cookbook doesn't seem to mention that), and the form protection component is one of the utilities that are ment to replace it (others are the CSRF middleware and the HTTPS enforcer middleware) - you shouldn't use both, drop the security component (and by that I mean to also remove the related loadComponent() call), and configure the form protection component accordingly instead!

$this->FormProtection->setConfig('unlockedActions', ['players']);

The docs really need some overhaul here, not only is there no deprecation notice for the security component, but also the form protection component isn't listed in the components section.

ndm
  • 59,784
  • 9
  • 71
  • 110