Question

I have a custom module with a defined route as:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="custom_module" frontName="custom-module">
            <module name="Custom_Module" />
        </route>
    </router>
</config>

With previous versions of Magento both GET and POST requests would work fine to http://mywebsite.com/custom-module/controllername

After upgrading to Magento 2.3.0, GET requests still work as before, however POST requests now do not call the execute() method of the controller. Instead, they respond with a 200 OK and a response body that is the homepage html of the website.

Does this have to do with some Csrf security feature and form keys that was added in v2.3?

Was it helpful?

Solution

Please check more generous solution that does not change core functionality, you can use around plugin on Validate function of Magento\Framework\App\Request\CsrfValidator class

This implementation does not break the core functionality of Magento 2.1/2.2/2.3 versions.

di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Request\CsrfValidator">
        <plugin name="csrf_validator_skip" type="Module\Vendor\Plugin\CsrfValidatorSkip" />
    </type>
</config>

CsrfValidatorSkip.php

<?php
namespace Module\Vendor\Plugin;
class CsrfValidatorSkip
{
    /**
     * @param \Magento\Framework\App\Request\CsrfValidator $subject
     * @param \Closure $proceed
     * @param \Magento\Framework\App\RequestInterface $request
     * @param \Magento\Framework\App\ActionInterface $action
     */
    public function aroundValidate(
        $subject,
        \Closure $proceed,
        $request,
        $action
    ) {
        if ($request->getModuleName() == 'Your_Module_frontName_Here') {
            return; // Skip CSRF check
        }
        $proceed($request, $action); // Proceed Magento 2 core functionalities
    }
}

Please star my Gist page at https://gist.github.com/ananth-iyer/59ecfabcbca73d6c2e3eeb986ed2f3c4 to encourage.

OTHER TIPS

UPDATE: I changed the accepted answer to the one from @AnanthMage2 as it follows Magento's coding practices


Found the solution, your controller must implement CsrfAwareActionInterface and 2 of its methods:

use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\Request\InvalidRequestException;

class MyController extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface
{
    public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
    {
        return null;
    }

    public function validateForCsrf(RequestInterface $request): ?bool
    {
        return true;
    }
}

This solves the problem, but is also backwards incompatible, i.e. your module will now not work on Magento 2.2 and earlier. To make it backwards compatible, something like the following is needed:

use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\Request\InvalidRequestException;

if (interface_exists("Magento\Framework\App\CsrfAwareActionInterface"))
    include __DIR__ . "/MyController.m230.php";
else
    include __DIR__ . "/MyController.m220.php";

Where you would have the full and correct class declaration in each of the two files.

Implement CsrfAwareActionInterface is a solution, but it makes the code not compatible with Magento < 2.3

Here is a trick (injecting the Key to the request on the Action) that is compatible with Magento 2.X

Put it in the constructor of the Action.

        // CsrfAwareAction Magento2.3 compatibility
        if (interface_exists("\Magento\Framework\App\CsrfAwareActionInterface")) {
            $request = $this->getRequest();
            if ($request instanceof HttpRequest && $request->isPost() && empty($request->getParam('form_key'))) {
                $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class);
                $request->setParam('form_key', $formKey->getFormKey());
            }
        }

One could provide a compatible solution to PHP 7.1< & Mage 2.3< if they were to outsource the validator to a different class, e.g.

if (PHP_VERSION_ID < 70100) {
    class Index extends Extendable\Main {}
} else {
    class Index extends Extendable\CsrCompatible {}
}

Where Extendable\Main has the logic for execute() {} and CsrCompatible can both extend Extendable\Main and implements CsrfAwareActionInterface. E.g:

class Main extends Action 
{
     execute() {...}
}

And

class CsrCompatible extends Main implements CsrfAwareActionInterface 
{
     //interface functions
}

The 5.6-7.0 will just never go into the CsrCompatible and therefore not throw an exception when it sees the fancy ?bool code.

I have added the implementation suggested by AnanthMage2 but was facing issue "$request->getModuleName() always return blank" as commented by sumeet bajaj. Major issue which we were facing was order's were not getting completed within magento after order was shipped within shipstation. So for temporary fix I have added below customization's within aroundValidate function:

class CsrfValidatorSkip {
    const CONTROLLER_MODULE = '/index.php/api/auctane';
    const CONTROLLER_NAME = '/api/auctane';

    /**
     * @param CsrfValidator $subject
     * @param Closure $proceed
     * @param RequestInterface $request
     * @param ActionInterface $action
     */
    public function aroundValidate(
        $subject,
        Closure $proceed,
        $request,
        $action
    ) {
        // Skip CSRF check
        /* if ($request->getControllerModule() == self::CONTROLLER_MODULE
            && $request->getControllerName() == self::CONTROLLER_NAME) {
            return;
        }*/
        $requestUrlCforge = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
        if ($requestUrlCforge == self::CONTROLLER_MODULE
            || $requestUrlCforge == self::CONTROLLER_NAME) {
            return;
        }


        // Proceed Magento 2 core functionalities
        $proceed($request, $action);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top