Middleware
Middleware objects give you the ability to ‘wrap’ your application in re-usable,composable layers of Request handling, or response building logic. Visually,your application ends up at the center, and middleware is wrapped aroud the applike an onion. Here we can see an application wrapped with Routes, Assets,Exception Handling and CORS header middleware.When a request is handled by your application it enters from the outermostmiddleware. Each middleware can either delegate the request/response to the nextlayer, or return a response. Returning a response prevents lower layers fromever seeing the request. An example of that is the AssetMiddleware handlinga request for a plugin image during development.If no middleware take action to handle the request, a controller will be locatedand have its action invoked, or an exception will be raised generating an errorpage.
Middleware are part of the new HTTP stack in CakePHP that leverages the PSR-7request and response interfaces. CakePHP also supports the PSR-15 standard forserver request handlers so you can use any PSR-15 compatible middleware availableon The Packagist.
Middleware in CakePHP
CakePHP provides several middleware to handle common tasks in web applications:
Cake\Error\Middleware\ErrorHandlerMiddleware
traps exceptions from thewrapped middleware and renders an error page using theError & Exception Handling Exception handler.Cake\Routing\AssetMiddleware
checks whether the request is referring to atheme or plugin asset file, such as a CSS, JavaScript or image file stored ineither a plugin’s webroot folder or the corresponding one for a Theme.Cake\Routing\Middleware\RoutingMiddleware
uses theRouter
to parse theincoming URL and assign routing parameters to the request.Cake\I18n\Middleware\LocaleSelectorMiddleware
enables automatic languageswitching from theAccept-Language
header sent by the browser.Cake\Http\Middleware\HttpsEnforcerMiddleware
requires HTTPS to be used.Cake\Http\Middleware\SecurityHeadersMiddleware
makes it easy to addsecurity related headers likeX-Frame-Options
to responses.Cake\Http\Middleware\EncryptedCookieMiddleware
gives you the ability tomanipulate encrypted cookies in case you need to manipulate cookie withobfuscated data.Cake\Http\Middleware\CsrfProtectionMiddleware
adds CSRF protection to yourapplication.Cake\Http\Middleware\BodyParserMiddleware
allows you to decode JSON, XMLand other encoded request bodies based onContent-Type
header.Cake\Http\Middleware\CspMiddleware
makes it simpler to addContent-Security-Policy headers to your application.
Using Middleware
Middleware can be applied to your application globally, or to individualrouting scopes.
To apply middleware to all requests, use the middleware
method of yourApp\Application
class. Your application’s middleware
hook method will becalled at the beginning of the request process, you can use theMiddlewareQueue
object to attach middleware:
- namespace App;
- use Cake\Http\BaseApplication;
- use Cake\Http\MiddlewareQueue;
- use Cake\Error\Middleware\ErrorHandlerMiddleware;
- class Application extends BaseApplication
- {
- public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
- {
- // Bind the error handler into the middleware queue.
- $middlwareQueue->add(new ErrorHandlerMiddleware());
- return $middlwareQueue;
- }
- }
In addition to adding to the end of the MiddlewareQueue
you can doa variety of operations:
- $layer = new \App\Middleware\CustomMiddleware;
- // Added middleware will be last in line.
- $middlwareQueue->add($layer);
- // Prepended middleware will be first in line.
- $middlwareQueue->prepend($layer);
- // Insert in a specific slot. If the slot is out of
- // bounds, it will be added to the end.
- $middlwareQueue->insertAt(2, $layer);
- // Insert before another middleware.
- // If the named class cannot be found,
- // an exception will be raised.
- $middlwareQueue->insertBefore(
- 'Cake\Error\Middleware\ErrorHandlerMiddleware',
- $layer
- );
- // Insert after another middleware.
- // If the named class cannot be found, the
- // middleware will added to the end.
- $middlwareQueue->insertAfter(
- 'Cake\Error\Middleware\ErrorHandlerMiddleware',
- $layer
- );
In addition to applying middleware to your entire application, you can applymiddleware to specific sets of routes usingScoped Middleware.
Adding Middleware from Plugins
Plugins can use their middleware
hook method to apply any middleware theyhave to the application’s middleware queue:
- // in plugins/ContactManager/src/Plugin.php
- namespace ContactManager;
- use Cake\Core\BasePlugin;
- use Cake\Http\MiddlewareQueue;
- use ContactManager\Middleware\ContactManagerContextMiddleware;
- class Plugin extends BasePlugin
- {
- public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
- {
- $middlwareQueue->add(new ContactManagerContextMiddleware());
- return $middlwareQueue;
- }
- }
Creating Middleware
Middleware can either be implemented as anonymous functions (Closures), or classeswhich extend Psr\Http\Server\MiddlewareInterface
. While Closures are suitablefor smaller tasks they make testing harder, and can create a complicatedApplication
class. Middleware classes in CakePHP have a few conventions:
- Middleware class files should be put in src/Middleware. For example:src/Middleware/CorsMiddleware.php
- Middleware classes should be suffixed with
Middleware
. For example:LinkMiddleware
. - Middleware must implement
Psr\Http\Server\MiddlewareInterface
.
Middleware can return a response either by calling $handler->handle()
or bycreating their own response. We can see both options in our simple middleware:
- // In src/Middleware/TrackingCookieMiddleware.php
- namespace App\Middleware;
- use Cake\Http\Cookie\Cookie;
- use Cake\I18n\Time;
- use Psr\Http\Message\ResponseInterface;
- use Psr\Http\Message\ServerRequestInterface;
- use Psr\Http\Server\RequestHandlerInterface;
- use Psr\Http\Server\MiddlewareInterface;
- class TrackingCookieMiddleware implements MiddlewareInterface
- {
- public function process(
- ServerRequestInterface $request,
- RequestHandlerInterface $handler
- ): ResponseInterface
- {
- // Calling $handler->handle() delegates control to the *next* middleware
- // In your application's queue.
- $response = $handler->handle($request);
- if (!$request->getCookie('landing_page')) {
- $expiry = new Time('+ 1 year');
- $response = $response->withCookie(new Cookie(
- 'landing_page',
- $request->getRequestTarget(),
- $expiry
- ));
- }
- return $response;
- }
- }
Now that we’ve made a very simple middleware, let’s attach it to ourapplication:
- // In src/Application.php
- namespace App;
- use App\Middleware\TrackingCookieMiddleware;
- use Cake\Http\MiddlewareQueue;
- class Application
- {
- public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
- {
- // Add your simple middleware onto the queue
- $middlwareQueue->add(new TrackingCookieMiddleware());
- // Add some more middleware onto the queue
- return $middlwareQueue;
- }
- }
Routing Middleware
Routing middleware is responsible for applying your application’s routes andresolving the plugin, controller, and action a request is going to. It can cachethe route collection used in your application to increase startup time. Toenable cached routes, provide the desired cache configuration as a parameter:
- // In Application.php
- public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
- {
- // ...
- $middlwareQueue->add(new RoutingMiddleware($this, 'routing'));
- }
The above would use the routing
cache engine to store the generated routecollection.
Security Header Middleware
The SecurityHeaderMiddleware
layer makes it easy to apply security relatedheaders to your application. Once setup the middleware can apply the followingheaders to responses:
X-Content-Type-Options
X-Download-Options
X-Frame-Options
X-Permitted-Cross-Domain-Policies
Referrer-Policy
This middleware is configured using a fluent interface before it is applied toyour application’s middleware stack:
- use Cake\Http\Middleware\SecurityHeadersMiddleware;
- $securityHeaders = new SecurityHeadersMiddleware();
- $securityHeaders
- ->setCrossDomainPolicy()
- ->setReferrerPolicy()
- ->setXFrameOptions()
- ->setXssProtection()
- ->noOpen()
- ->noSniff();
- $middlewareQueue->add($securityHeaders);
Content Security Policy Header Middleware
The CspMiddleware
makes it simpler to add Content-Security-Policy headers inyour application. Before using it you should install paragonie/csp-builder
:
You can then configure the middleware using an array, or passing in a builtCSPBuilder
object:
- use Cake\Http\Middleware\CspMiddleware;
- $csp = new CspMiddleware([
- 'script-src' => [
- 'allow' => [
- 'https://www.google-analytics.com',
- ],
- 'self' => true,
- 'unsafe-inline' => false,
- 'unsafe-eval' => false,
- ],
- ]);
- $middlewareQueue->add($csp);
Encrypted Cookie Middleware
If your application has cookies that contain data you want to obfuscate andprotect against user tampering, you can use CakePHP’s encrypted cookiemiddleware to transparently encrypt and decrypt cookie data via middleware.Cookie data is encrypted with via OpenSSL using AES:
- use Cake\Http\Middleware\EncryptedCookieMiddleware;
- $cookies = new EncryptedCookieMiddleware(
- // Names of cookies to protect
- ['secrets', 'protected'],
- Configure::read('Security.cookieKey')
- );
- $middlwareQueue->add($cookies);
Note
It is recommended that the encryption key you use for cookie data, is usedexclusively for cookie data.
The encryption algorithms and padding style used by the cookie middleware arebackwards compatible with CookieComponent
from earlier versions of CakePHP.
Cross Site Request Forgery (CSRF) Middleware
CSRF protection can be applied to your entire application, or to specific routing scopes.
Note
You cannot use both of the following approaches together, you must chooseonly one. If you use both approaches together, a CSRF token mismatch errorwill occur on every PUT and POST request
By applying the CsrfProtectionMiddleware
to your Application middlewarestack you protect all the actions in application:
- // in src/Application.php
- use Cake\Http\Middleware\CsrfProtectionMiddleware;
- public function middleware($middlwareQueue) {
- $options = [
- // ...
- ];
- $csrf = new CsrfProtectionMiddleware($options);
- $middlwareQueue->add($csrf);
- return $middlwareQueue;
- }
By applying the CsrfProtectionMiddleware
to routing scopes, you can includeor exclude specific route groups:
- // in src/Application.php
- use Cake\Http\Middleware\CsrfProtectionMiddleware;
- public function routes($routes) {
- $options = [
- // ...
- ];
- $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
- parent::routes($routes);
- }
- // in config/routes.php
- Router::scope('/', function (RouteBuilder $routes) {
- $routes->applyMiddleware('csrf');
- });
Options can be passed into the middleware’s constructor.The available configuration options are:
cookieName
The name of the cookie to send. Defaults tocsrfToken
.expiry
How long the CSRF token should last. Defaults to browser session.secure
Whether or not the cookie will be set with the Secure flag. That is,the cookie will only be set on a HTTPS connection and any attempt over normal HTTPwill fail. Defaults tofalse
.httpOnly
Whether or not the cookie will be set with the HttpOnly flag. Defaults tofalse
.field
The form field to check. Defaults to_csrfToken
. Changing thiswill also require configuring FormHelper.
When enabled, you can access the current CSRF token on the request object:
- $token = $this->request->getAttribute('_csrfToken');
You can use the whitelisting callback feature for more fine grained control overURLs for which CSRF token check should be done:
- // in src/Application.php
- use Cake\Http\Middleware\CsrfProtectionMiddleware;
- public function middleware($middlewareQueue) {
- $csrf = new CsrfProtectionMiddleware();
- // Token check will be skipped when callback returns `true`.
- $csrf->whitelistCallback(function ($request) {
- // Skip token check for API URLs.
- if ($request->getParam('prefix') === 'Api') {
- return true;
- }
- });
- // Ensure routing middleware is added to the queue before CSRF protection middleware.
- $middlewareQueue->add($csrf);
- return $middlewareQueue;
- }
Note
You should apply the CSRF protection middleware only for URLs which handle statefulrequests using cookies/session. Stateless requests, for e.g. when developing an API,are not affected by CSRF so the middleware does not need to be applied for those URLs.
Integration with FormHelper
The CsrfProtectionMiddleware
integrates seamlessly with FormHelper
. Eachtime you create a form with FormHelper
, it will insert a hidden field containingthe CSRF token.
Note
When using CSRF protection you should always start your forms with theFormHelper
. If you do not, you will need to manually create hidden inputs ineach of your forms.
CSRF Protection and AJAX Requests
In addition to request data parameters, CSRF tokens can be submitted througha special X-CSRF-Token
header. Using a header often makes it easier tointegrate a CSRF token with JavaScript heavy applications, or XML/JSON based APIendpoints.
The CSRF Token can be obtained via the Cookie csrfToken
.
Body Parser Middleware
If your application accepts JSON, XML or other encoded request bodies, theBodyParserMiddleware
will let you decode those requests into an array thatis available via $request->getParsedData()
and $request->getData()
. Bydefault only json
bodies will be parsed, but XML parsing can be enabled withan option. You can also define your own parsers:
- use Cake\Http\Middleware\BodyParserMiddleware;
- // only JSON will be parsed.
- $bodies = new BodyParserMiddleware();
- // Enable XML parsing
- $bodies = new BodyParserMiddleware(['xml' => true]);
- // Disable JSON parsing
- $bodies = new BodyParserMiddleware(['json' => false]);
- // Add your own parser matching content-type header values
- // to the callable that can parse them.
- $bodies = new BodyParserMiddleware();
- $bodies->addParser(['text/csv'], function ($body, $request) {
- // Use a CSV parsing library.
- return Csv::parse($body);
- });
HTTPS Enforcer Middleware
If you want your application to only be available via HTTPS connections you canuse the HttpsEnforcerMiddleware
:
- use Cake\Http\Middleware\HttpsEnforcerMiddleware;
- // Always raise an exception and never redirect.
- $https = new HttpsMiddleware([
- 'redirect' => false,
- ]);
- // Send a 302 status code when redirecting
- $https = new HttpsMiddleware([
- 'redirect' => true,
- 'statusCode' => 302,
- ]);
- // Send additional headers in the redirect response.
- $https = new HttpsMiddleware([
- 'headers' => ['X-Https-Upgrade', => true],
- ]);
- // Disable HTTPs enforcement when ``debug`` is on.
- $https = new HttpsMiddleware([
- 'disableOnDebug' => true,
- ]);
If a non-HTTPs request is received that doesn’t use GETa BadRequestException
will be raised.