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:
- 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 route
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.
https://github.com/cakephp/cakephp/issues/17984
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\MiddlewareQueue
docs: CakePHP 5, CakePHP 4, CakePHP 3
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
-
Create a new method called
routes
insrc/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 undercsrf
alias. -
Enable the above
csrf
for 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_