Use HELMET as PSR-15 middleware#
This page mounts the same HELMET core in a PSR-15 middleware pipeline. Copy the pipeline in Mount the pipeline, then decide which middlewares your app needs and how errors should flow.
Mount the pipeline#
The same core, mounted in a middleware pipeline:
use Nyholm\Psr7\Factory\Psr17Factory;
use Waynestate\Helmet\Csrf\RequestForgeryGuard;
use Waynestate\Helmet\Headers\SecurityHeadersMiddleware;
use Waynestate\Helmet\Health\HealthCheck;
use Waynestate\Helmet\Platform\Presets;
use Waynestate\Helmet\Proxy\TrustedProxies;
$factory = new Psr17Factory();
$proxies = new TrustedProxies(Presets::proxies('citadel'));
$middleware = [
new HealthCheck($factory, $factory, '/up'),
new RequestForgeryGuard($proxies, $factory, $factory, except: ['/api/webhooks/*']),
new SecurityHeadersMiddleware($proxies),
];
The middlewares#
HealthCheckanswers the health path,/upin the example.RequestForgeryGuardis opt-in: mounting it turns the check on for that pipeline. The example passesexcept: ['/api/webhooks/*']. Its options are described in The request-forgery guard.SecurityHeadersMiddlewaresets the request attributehelmet.csp_nonceso a downstream handler can read the same nonce the CSP header carries.
On every request, SecurityHeadersMiddleware:
- decides whether the request is HTTPS from the trusted-proxy list,
- builds the headers, including a per-request CSP nonce,
- exposes the nonce to the application as the
helmet.csp_noncerequest attribute, - removes the
X-Powered-Byheader.
The headers are sent even when the inner handler throws. When no-store is enabled and the request path matches (noStorePaths is empty for every response) and the response carries no Cache-Control header, the middleware adds Cache-Control: no-store.
Errors and rethrow#
By default the middleware rethrows, so the framework's error handler still runs and produces the error page, and the middleware attaches the headers only to responses that come back through it; pass rethrow: false (and a PSR-17 ResponseFactory) to make the middleware itself return a 500 carrying the same headers. The raw PHP path needs no such flag: helmet_boot() sends the headers before your code runs, so they are already on the wire by the time an exception can happen.
The 500 response carries Content-Type: text/plain; charset=utf-8. If rethrow is false and no ResponseFactory was passed, the middleware throws a \RuntimeException when it needs to build the 500.
Constructor options#
| Option | Default | Meaning |
|---|---|---|
proxies |
required | the TrustedProxies list used to decide whether the request is HTTPS |
cspPolicy |
null |
merged per directive over the default policy; a directive set to null is removed; null uses the default policy unchanged |
rethrow |
true |
rethrow when the inner handler throws so the framework's error handler runs; when false, return a 500 with the security headers instead |
responseFactory |
null |
builds the 500 response when rethrow is false; only needed on that path |
hstsMaxAge |
31536000 |
the HSTS max-age in seconds, sent only on HTTPS |
hstsIncludeSubDomains |
true |
whether HSTS covers subdomains, sent only on HTTPS |
cspReportUri |
null |
the CSP report destination, or null to send no reporting directives |
cspReportOnly |
false |
when true, the policy is sent as Content-Security-Policy-Report-Only instead of Content-Security-Policy |
coop |
'same-origin' |
the Cross-Origin-Opener-Policy value, or null to send no COOP header |
corp |
null |
the Cross-Origin-Resource-Policy value, or null to send no CORP header |
permissionsPolicy |
null |
the Permissions-Policy feature map, or null to use the default deny list; an empty array sends no Permissions-Policy header |
hstsPreload |
false |
whether to append the preload directive to HSTS, sent only on HTTPS |
trustedTypesEnabled |
false |
whether to append the Trusted Types directives to the report-only CSP header |
trustedTypesPolicies |
['default'] |
the Trusted Types policy names, used when trustedTypesEnabled is true |
noStoreEnabled |
false |
whether to send Cache-Control: no-store on matching responses |
noStorePaths |
[] |
path prefixes that carry no-store, or empty for every response |