CakePHP PSR-15 middleware order: CSRF, authentication, and short-circuiting
Middleware registration order in CakePHP determines whether CSRF validation runs before authentication and whether custom logic can short-circuit a request.
20 Jun 2026, 04:26 UTC

The problem: CSRF fails, or your middleware never runs
You add CakePHP's CSRF protection and the Authentication plugin to a 4.x or 5.x application, then POST to a login form. The request fails with a CSRF token mismatch, or your custom middleware appears to be ignored. The issue is usually not the plugins; it is the position of each layer in CakePHP's PSR-15 pipeline.
PSR-15 is a PHP standard for HTTP server middleware. A middleware class implements Psr\Http\Server\MiddlewareInterface with a process() method that receives a request and a handler, then returns a response. CakePHP composes these layers with Http\MiddlewareQueue and runs them through Http\Runner.
The useful rule: the first middleware you register wraps the outside of the request. It runs first on the way in and last on the way out. CSRF validation must precede authentication and any state-changing logic, and a middleware that returns a response without calling the handler short-circuits everything inside it.
How the pipeline wraps a request
When a request arrives, CakePHP's Runner passes it through the queue in registration order. Each middleware can inspect or change the request, then call $handler->handle($request) to pass control inward. The controller action sits at the center. On the return trip, each middleware can modify the response before the previous layer sees it.
| Position | Middleware | Role |
|---|---|---|
| 1 | ErrorHandlerMiddleware | Catches exceptions and renders error responses |
| 2 | AssetMiddleware | Serves webroot assets directly |
| 3 | RoutingMiddleware | Parses routes and connects controller and action |
| 4 | CsrfProtectionMiddleware | Validates CSRF tokens on state-changing requests |
| 5 | AuthenticationMiddleware | Extracts credentials and attaches identity to the request |
| 6 | Custom application middleware | Your business logic, maintenance mode, or rate limiting |
| 7 | Controller action | Final handler that produces the response |
This is a common stack, not a guaranteed default. Your src/Application.php may already register some of these layers, so inspect the existing middleware() method before adding duplicates.
Register middleware in Application::middleware()
Open src/Application.php in your project root. You need normal file-write permissions; no sudo is required unless your vendor directory was installed as root. Add layers in the order you want them to execute on the way in.
namespace App;
use App\Middleware\MaintenanceMiddleware;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\MiddlewareQueue;
class Application extends BaseApplication
{
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
->add(new \Cake\Http\Middleware\ErrorHandlerMiddleware())
->add(new \Cake\Http\Middleware\AssetMiddleware())
->add(new \Cake\Http\Middleware\RoutingMiddleware());
$middlewareQueue->add(new CsrfProtectionMiddleware());
$middlewareQueue->add(new AuthenticationMiddleware());
$middlewareQueue->add(new MaintenanceMiddleware());
return $middlewareQueue;
}
}
Middleware added here runs for every request, including CLI and console tasks. If a layer should only apply to web traffic, guard it inside process():
if ($request->is('cli')) {
return $handler->handle($request);
}
Worked example: maintenance mode that short-circuits
Create src/Middleware/MaintenanceMiddleware.php. This middleware checks for a flag file and returns a 503 response without calling the handler, so the controller and inner middleware never run.
namespace App\Middleware;
use Cake\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class MaintenanceMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->is('cli')) {
return $handler->handle($request);
}
if (file_exists(CONFIG . 'maintenance.enable')) {
$response = new Response();
return $response
->withStatus(503)
->withType('text/html')
->withStringBody('Maintenance mode');
}
return $handler->handle($request);
}
}
Cake\Http\Response implements PSR-7 ResponseInterface, so it is compatible with CakePHP's runner. Because the middleware returns early, it bypasses CSRF, authentication, and the controller. Place it after CSRF if you want token validation to run first, or before CSRF if maintenance mode should block token validation entirely.
To verify, create config/maintenance.enable in the project root and request any URL. You should receive HTTP 503 with the maintenance body. Remove the file and reload; the application should respond normally. This is a configuration change, so rollback means deleting the flag file or removing the middleware registration.
Limitations and how to check the result
- Session data: Session middleware is not included by default in CakePHP 4.x or 5.x. Add it manually if your middleware needs session data before the controller runs.
- Third-party response prototypes: Some external PSR-15 middleware expects a specific response implementation, such as Diactoros. CakePHP uses Cake\Http\Response; verify compatibility or wrap the response before passing it along.
- Exception handling: Calling $handler->handle() multiple times or throwing outside ErrorHandlerMiddleware produces unexpected results. Wrap delegate calls in try/catch if you need custom error behavior.
Practical checks:
- Add error_log('middleware: ' . static::class) to each custom process() method, make a request, and confirm the log order matches your registration order on the way in.
- Use CakePHP's IntegrationTestTrait to simulate a request through the full stack and assert the returned status code or request attributes.
- Run vendor/bin/phpunit --filter MiddlewareTest from the project root to exercise core middleware tests. This requires development dependencies installed and normal user permissions.
Closing
Inspect src/Application.php today. Confirm CSRF protection sits before authentication, place short-circuiting middleware where it makes sense for your traffic, and verify execution order with logs or integration tests before deploying.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.