CakePHP 4 "Missing or incorrect CSRF cookie type"

in CakePHP


The Problem

You must be making a non-GET (e.g. POST) request to an endpoint protected by Cake\Http\Middleware\CsrfProtectionMiddleware. Most likely it is enabled on the application level in your src/Application.php.

Previously, with CsrfComponent in CakePHP 3, you could have unlockedActions. In case of this new middleware, you have two options:

  1. Add exception when adding the middleware to the queue.
  2. Disable the middleware completely, and enable it for the parts of your application where you need it.

1. Adding an exception route

Quick links to the documentation:

Go to src/Application.php and find the middleware() method. By default, you will see a chain of $middlewareQueue->add() calls. The last one will add new CsrfProtectionMiddleware. You will have to break the chain, and manipulate your instance of this CsrfProtectionMiddleware by adding an exception, before adding it to the queue.

So your middleware() method will probably look like this.

$middlewareQueue
    // A bunch of standard add() calls go here
    ->add(…)
    ->add(…)
    // Keep routing added before before CSRF!
    ->add(new RoutingMiddleware($this))
    // Now, delete the last add() containing new CsrfProtectionMiddleware
    ;

// Initialise CsrfProtectionMiddleware as separate variable we can work with
$csrf = new CsrfProtectionMiddleware([
    'httponly' => true,
]);
// Token check will be skipped when callback returns `true`.
$csrf->skipCheckCallback(function ($request) {
    // Skip token check for API URLs.
    if ($request->getParam('prefix') === 'Api') {
        return true;
    }
});
// Ensure routing middleware has already been added to the queue
$middlewareQueue->add($csrf);

return $middlewareQueue;

(For bonus points) If you need to do this from a plugin

Imagine you're working on a plugin that needs to add a prefix, and that prefix has to be exempt from the CSRF checks.

https://github.com/cakephp/cakephp/issues/17984

Documentation:

2. Disable-Reenable route

Disable CSRF

To disable the application-level protection completely, remove it from Application::middleware in src/Application.php.

-    // Cross Site Request Forgery (CSRF) Protection Middleware
-    // https://book.cakephp.org/4/en/controllers/middleware.html#cross-site-request-forgery-csrf-middleware
-    ->add(new CsrfProtectionMiddleware([
-     'httponly' => true,
-    ]));
+    ;

(Do not forget to put back the trailing ;)

Re-enable for specific prefixes

  1. Create a new method called routes in src/Application.php with the following contents. You can add it last.

    public function routes(\Cake\Routing\RouteBuilder $routes) : void
    {
        // Cross Site Request Forgery (CSRF) Protection Middleware
        // https://book.cakephp.org/4/en/controllers/middleware.html#cross-site-request-forgery-csrf-middleware
        $options = [
         'httponly' => true,
        ];
        $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
        parent::routes($routes);
    }

    CsrfProtectionMiddleware is now registered and available under csrf alias.

  2. Enable the above csrf for specific routes and prefixes. In config/routes.php:

    $routes->scope('/', function (RouteBuilder $builder) {
        $builder->applyMiddleware('csrf');
        // …
    });
    
    $routes->prefix('Admin', function (RouteBuilder $routes) {
        $routes->applyMiddleware('csrf');
        $routes->fallbacks(DashedRoute::class);
    });
  3. You can skip applying the middleware for the prefixes where you do not need it, e.g. /api:

    $routes->prefix('Api', function (RouteBuilder $routes) {
        $routes->setExtensions(['json']);
        $routes->fallbacks(DashedRoute::class);
    });
  4. As always, clear the cache before testing:

    bin/cake cache clear _cake_routes_
#cakephp #middleware