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.
../_images/middleware-setup.png
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.
../_images/middleware-request.png
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. Because CakePHP is leveraging the PSR-7standard you can use any PSR-7 compatible middleware available on 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 the Router to parse theincoming URL and assign routing parameters to the request.
  • Cake\I18n\Middleware\LocaleSelectorMiddleware enables automatic languageswitching from the Accept-Language header sent by the browser.
  • Cake\Http\Middleware\SecurityHeadersMiddleware makes it easy to addsecurity related headers like X-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 on Content-Type header.

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. If you don’t have an App\Application class, seethe section on Adding the new HTTP Stack to an Existing Application for more information. Your application’smiddleware hook method will be called at the beginning of the requestprocess, you can use the MiddlewareQueue object to attach middleware:

  1. namespace App;
  2.  
  3. use Cake\Http\BaseApplication;
  4. use Cake\Error\Middleware\ErrorHandlerMiddleware;
  5.  
  6. class Application extends BaseApplication
  7. {
  8. public function middleware($middlewareQueue)
  9. {
  10. // Bind the error handler into the middleware queue.
  11. $middlewareQueue->add(new ErrorHandlerMiddleware());
  12. return $middlewareQueue;
  13. }
  14. }

In addition to adding to the end of the MiddlewareQueue you can doa variety of operations:

  1. $layer = new \App\Middleware\CustomMiddleware;
  2.  
  3. // Added middleware will be last in line.
  4. $middlewareQueue->add($layer);
  5.  
  6. // Prepended middleware will be first in line.
  7. $middlewareQueue->prepend($layer);
  8.  
  9. // Insert in a specific slot. If the slot is out of
  10. // bounds, it will be added to the end.
  11. $middlewareQueue->insertAt(2, $layer);
  12.  
  13. // Insert before another middleware.
  14. // If the named class cannot be found,
  15. // an exception will be raised.
  16. $middlewareQueue->insertBefore(
  17. 'Cake\Error\Middleware\ErrorHandlerMiddleware',
  18. $layer
  19. );
  20.  
  21. // Insert after another middleware.
  22. // If the named class cannot be found, the
  23. // middleware will added to the end.
  24. $middlewareQueue->insertAfter(
  25. 'Cake\Error\Middleware\ErrorHandlerMiddleware',
  26. $layer
  27. );

In addition to applying middleware to your entire application, you can applymiddleware to specific sets of routes usingScoped Middleware.

Adding Middleware from Plugins

After the middleware queue has been prepared by the application, theServer.buildMiddleware event is triggered. This event can be useful to addmiddleware from plugins. Plugins can register listeners in their bootstrapscripts, that add middleware:

  1. // In ContactManager plugin bootstrap.php
  2. use Cake\Event\EventManager;
  3.  
  4. EventManager::instance()->on(
  5. 'Server.buildMiddleware',
  6. function ($event, $middlewareQueue) {
  7. $middlewareQueue->add(new ContactPluginMiddleware());
  8. });

PSR-7 Requests and Responses

Middleware and the new HTTP stack are built on top of the PSR-7 Request& Response Interfaces. While allmiddleware will be exposed to these interfaces, your controllers, components,and views will not.

Interacting with Requests

The RequestInterface provides methods for interacting with the headers,method, URI, and body of a request. To interact with the headers, you can:

  1. // Read a header as text
  2. $value = $request->getHeaderLine('Content-Type');
  3.  
  4. // Read header as an array
  5. $value = $request->getHeader('Content-Type');
  6.  
  7. // Read all the headers as an associative array.
  8. $headers = $request->getHeaders();

Requests also give access to the cookies and uploaded files they contain:

  1. // Get an array of cookie values.
  2. $cookies = $request->getCookieParams();
  3.  
  4. // Get a list of UploadedFile objects
  5. $files = $request->getUploadedFiles();
  6.  
  7. // Read the file data.
  8. $files[0]->getStream();
  9. $files[0]->getSize();
  10. $files[0]->getClientFileName();
  11.  
  12. // Move the file.
  13. $files[0]->moveTo($targetPath);

Requests contain a URI object, which contains methods for interacting with therequested URI:

  1. // Get the URI
  2. $uri = $request->getUri();
  3.  
  4. // Read data out of the URI.
  5. $path = $uri->getPath();
  6. $query = $uri->getQuery();
  7. $host = $uri->getHost();

Lastly, you can interact with a request’s ‘attributes’. CakePHP uses theseattributes to carry framework specific request parameters. There are a fewimportant attributes in any request handled by CakePHP:

  • base contains the base directory for your application if there is one.
  • webroot contains the webroot directory for your application.
  • params contains the results of route matching once routing rules have beenprocessed.
  • session contains an instance of CakePHP’s Session object. SeeAccessing the Session Object for more information on how to use the sessionobject.

Interacting with Responses

The methods available to create a server response are the same as thoseavailable when interacting with Response Objects. While theinterface is the same the usage scenarios are different.

When modifying the response, it is important to remember that responses areimmutable. You must always remember to store the results of any settermethod. For example:

  1. // This does *not* modify $response. The new object was not
  2. // assigned to a variable.
  3. $response->withHeader('Content-Type', 'application/json');
  4.  
  5. // This works!
  6. $newResponse = $response->withHeader('Content-Type', 'application/json');

Most often you’ll be setting headers and response bodies on requests:

  1. // Assign headers and a status code
  2. $response = $response->withHeader('Content-Type', 'application/json')
  3. ->withHeader('Pragma', 'no-cache')
  4. ->withStatus(422);
  5.  
  6. // Write to the body
  7. $body = $response->getBody();
  8. $body->write(json_encode(['errno' => $errorCode]));

Creating Middleware

Middleware can either be implemented as anonymous functions (Closures), or asinvokable classes. While Closures are suitable for smaller tasks they maketesting harder, and can create a complicated Application class. Middlewareclasses 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 are expected to implement the middleware protocol.
    While not a formal interface (yet), Middleware do have a soft-interface or‘protocol’. The protocol is as follows:

  • Middleware must implement __invoke($request, $response, $next).

  • Middleware must return an object implementing the PSR-7 ResponseInterface.
    Middleware can return a response either by calling $next or by creatingtheir own response. We can see both options in our simple middleware:
  1. // In src/Middleware/TrackingCookieMiddleware.php
  2. namespace App\Middleware;
  3.  
  4. use Cake\Http\Cookie\Cookie;
  5. use Cake\I18n\Time;
  6.  
  7. class TrackingCookieMiddleware
  8. {
  9. public function __invoke($request, $response, $next)
  10. {
  11. // Calling $next() delegates control to the *next* middleware
  12. // In your application's queue.
  13. $response = $next($request, $response);
  14.  
  15. // When modifying the response, you should do it
  16. // *after* calling next.
  17. if (!$request->getCookie('landing_page')) {
  18. $expiry = new Time('+ 1 year');
  19. $response = $response->withCookie(new Cookie(
  20. 'landing_page',
  21. $request->getRequestTarget(),
  22. $expiry
  23. ));
  24. }
  25. return $response;
  26. }
  27. }

Now that we’ve made a very simple middleware, let’s attach it to ourapplication:

  1. // In src/Application.php
  2. namespace App;
  3.  
  4. use App\Middleware\TrackingCookieMiddleware;
  5.  
  6. class Application
  7. {
  8. public function middleware($middlewareQueue)
  9. {
  10. // Add your simple middleware onto the queue
  11. $middlewareQueue->add(new TrackingCookieMiddleware());
  12.  
  13. // Add some more middleware onto the queue
  14.  
  15. return $middlewareQueue;
  16. }
  17. }

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:

  1. // In Application.php
  2. public function middleware($middlewareQueue)
  3. {
  4. // ...
  5. $middlewareQueue->add(new RoutingMiddleware($this, 'routing'));
  6. }

The above would use the routing cache engine to store the generated routecollection.

New in version 3.6.0: Route caching was added in 3.6.0

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:
  1. use Cake\Http\Middleware\SecurityHeadersMiddleware;
  2.  
  3. $securityHeaders = new SecurityHeadersMiddleware();
  4. $securityHeaders
  5. ->setCrossDomainPolicy()
  6. ->setReferrerPolicy()
  7. ->setXFrameOptions()
  8. ->setXssProtection()
  9. ->noOpen()
  10. ->noSniff();
  11.  
  12. $middlewareQueue->add($securityHeaders);

New in version 3.5.0: The SecurityHeadersMiddleware was added in 3.5.0

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:

  1. use Cake\Http\Middleware\EncryptedCookieMiddleware;
  2.  
  3. $cookies = new EncryptedCookieMiddleware(
  4. // Names of cookies to protect
  5. ['secrets', 'protected'],
  6. Configure::read('Security.cookieKey')
  7. );
  8.  
  9. $middlewareQueue->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.

New in version 3.5.0: The EncryptedCookieMiddleware was added in 3.5.0

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 choose only one.If you use both approaches together, a CSRF token mismatch error will occur on every PUT and POST request

Warning

You cannot use CsrfComponent together with CsrfProtectionMiddleware, the warning about conflicting components is not shown until 3.7.0.

By applying the CsrfProtectionMiddleware to your Application middleware stack you protect all the actions in application:

  1. // in src/Application.php
  2. use Cake\Http\Middleware\CsrfProtectionMiddleware;
  3.  
  4. public function middleware($middlewareQueue) {
  5. $options = [
  6. // ...
  7. ];
  8. $csrf = new CsrfProtectionMiddleware($options);
  9.  
  10. $middlewareQueue->add($csrf);
  11. return $middlewareQueue;
  12. }

By applying the CsrfProtectionMiddleware to routing scopes, you can include or exclude specific route groups:

  1. // in src/Application.php
  2. use Cake\Http\Middleware\CsrfProtectionMiddleware;
  3.  
  4. public function routes($routes) {
  5. $options = [
  6. // ...
  7. ];
  8. $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
  9. parent::routes($routes);
  10. }
  11.  
  12. // in config/routes.php
  13. Router::scope('/', function (RouteBuilder $routes) {
  14. $routes->applyMiddleware('csrf');
  15. });

Options can be passed into the middleware’s constructor.The available configuration options are:

  • cookieName The name of the cookie to send. Defaults to csrfToken.
  • 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 to false.
  • httpOnly Whether or not the cookie will be set with the HttpOnly flag. Defaults to false.
  • 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:
  1. $token = $this->request->getParam('_csrfToken');

New in version 3.5.0: The CsrfProtectionMiddleware was added in 3.5.0

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:

  1. use Cake\Http\Middleware\BodyParserMiddleware;
  2.  
  3. // only JSON will be parsed.
  4. $bodies = new BodyParserMiddleware();
  5.  
  6. // Enable XML parsing
  7. $bodies = new BodyParserMiddleware(['xml' => true]);
  8.  
  9. // Disable JSON parsing
  10. $bodies = new BodyParserMiddleware(['json' => false]);
  11.  
  12. // Add your own parser matching content-type header values
  13. // to the callable that can parse them.
  14. $bodies = new BodyParserMiddleware();
  15. $bodies->addParser(['text/csv'], function ($body, $request) {
  16. // Use a CSV parsing library.
  17. return Csv::parse($body);
  18. });

New in version 3.6.0: The BodyParserMiddleware was added in 3.6.0