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 ways to approach this:
- Add exception when adding the middleware to the queue.
- Disable the middleware completely, and enable it for the parts of your application where you need it.
1. 'Adding an exception' way
Quick links to the documentation:
- CakePHP 3: https://book.cakephp.org/3/en/controllers/components/security.html#disabling-security-component-for-specific-actions
- CakePHP 4: https://book.cakephp.org/4/en/security/csrf.html#skipping-csrf-checks-for-specific-actions
- CakePHP 5: https://book.cakephp.org/5/en/security/csrf.html#skipping-csrf-checks-for-specific-actions
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. In your ExamplePlugin/src/ExamplePlugin.php:
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// Loop through queue directly using Iterator capabilities of MiddlewareQueue
foreach ($middlewareQueue as $layer) {
// Skip right away if this is not a CSRF layer
if (!($layer instanceof \Cake\Http\Middleware\CsrfProtectionMiddleware)) {
continue;
}
// Add the callback
$layer->skipCheckCallback(function ($request) {
// Adjust the condition to your needs; I had to allow specific prefix in current plugin
if ($request->getParam('plugin') === $this->getName() && $request->getParam('prefix') === 'Api') {
return true;
}
});
// Once we found the CSRF layer, stop the loop as there's no need to continue
break;
}
return $middlewareQueue;
}
Note: prefix you're comparing to needs to match case in which you defined it. If in ExamplePlugin/config/routes.php you defined $routes->prefix('Api', ...), then you do $request->getParam('prefix') === 'Api', not lowercase api.
Further reading and context:
Documentation:
- CakePHP 4 and 5: https://book.cakephp.org/5/en/controllers/middleware.html#adding-middleware-from-plugins
- Same as the above, but for CakePHP 3: https://book.cakephp.org/3/en/controllers/middleware.html#adding-middleware-from-plugins
\Cake\Http\MiddlewareQueuedocs: CakePHP 5, CakePHP 4, CakePHP 3
2. 'Disable-Reenable' way
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
-
Create a new method called
routesinsrc/Application.phpwith 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); }CsrfProtectionMiddlewareis now registered and available undercsrfalias. -
Enable the above
csrffor specific routes and prefixes. Inconfig/routes.php:$routes->scope('/', function (RouteBuilder $builder) { $builder->applyMiddleware('csrf'); // … }); $routes->prefix('Admin', function (RouteBuilder $routes) { $routes->applyMiddleware('csrf'); $routes->fallbacks(DashedRoute::class); }); -
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); }); -
As always, clear the cache before testing:
bin/cake cache clear _cake_routes_