Tuesday, January 25, 2022

[FIXED] CSRF field is missing when I embed my form with a requestAction in CakePHP 3

Issue

I want to embed a contact form in multiple places on my website.

I developed a contact form in a contact() function within my MessagesController.php:

// MessagesController.php
public function contact()
{
    $this->set('title', 'Contact');
    $message = $this->Messages->newEntity();
    ... // shortened for brevity
    $this->set(compact('message'));
    $this->set('_serialize', ['message']);
}

I loaded the CSRF component in the initialize() function of the AppController.php:

// AppController.php
public function initialize()
{
    parent::initialize();
    $this->loadComponent('Csrf');
    ... // shortened for brevity
}

The form is rendered with a contact.ctp and it works fine.

I followed CakePHP's cookbook which suggests using requestAction() within an element, then echoing the element where I want it:

// contact_form.ctp
<?php
    echo $this->requestAction(
        ['controller' => 'Messages', 'action' => 'contact']
    );
?>

And:

// home.ctp
<?= $this->element('contact_form'); ?>

The problem is that the form is rendered fine, but the CSRF hidden field is missing. It should be automatically added to the form since the CSRF component is called in the AppController.php.

I guess either using an element with a requestAction() isn't the solution for this particular case, or I am doing something wrong.

Any ideas? Thanks in advance for the input!


Solution

Request parameters need to be passed manually

requestAction() uses a new \Cake\Network\Request instance, and it doesn't pass the _Token and _csrf parameters to it, so that's why things break.

While you could pass them yourself via the $extra argument, like

$this->requestAction(
    ['controller' => 'Messages', 'action' => 'contact'],
    [
        '_Token' => $this->request->param('_Token'),
        '_csrf' => $this->request->param('_csrf')
    ]
);

Use a cell instead

I would suggest using a cell instead, which is way more lightweight than requesting an action, also it operates in the current request and thus will work with the CSRF component out of the box.

You'd pretty much just need to copy your controller action code (as far as the code is concerned that you are showing), and add a loadModel() call to load the Messages table, something like

src/View/Cell/ContactFormCell.php

namespace App\View\Cell;

use Cake\View\Cell;

class ContactFormCell extends Cell
{
    public function display()
    {
        $this->loadModel('Messages');

        $this->set('title', 'Contact');
        $message = $this->Messages->newEntity();
        // ... shortened for brevity
        $this->set(compact('message'));
        $this->set('_serialize', ['message']);
    }
}

Create the form in the corresponding cell template

src/Template/Cell/ContactForm/display.ctp

<?php
echo $this->Form->create(
    /* ... */,
    // The URL needs to be set explicitly, as the form is being
    // created in the context of the current request
    ['url' => ['controller' => 'Messages', 'action' => 'contact']]
);
// ...

And then wherever you want to place the form, just use <?= $this->cell('ContactForm') ?>.

See also



Answered By - ndm

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.