Laravel middleware for comprehensive security headers including CSP with nonce support, HSTS, and Permissions-Policy
composer require philiprehberger/laravel-security-headersLaravel middleware for comprehensive security headers including CSP with nonce support, HSTS, and Permissions-Policy.
composer require philiprehberger/laravel-security-headers
Laravel auto-discovery registers the service provider automatically.
php artisan vendor:publish --tag=security-headers-config
This copies config/security-headers.php into your application's config/ directory.
use PhilipRehberger\SecurityHeaders\SecurityHeaders;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
SecurityHeaders::class,
]);
})
protected $middlewareGroups = [
'web' => [
// ...
\PhilipRehberger\SecurityHeaders\SecurityHeaders::class,
],
];
The nonce is shared to every view under the variable name configured in csp.nonce_view_variable (default: cspNonce).
<script nonce="{{ $cspNonce }}">
console.log('Inline script allowed by CSP nonce');
</script>
<style nonce="{{ $cspNonce }}">
/* Inline styles allowed by CSP nonce */
</style>
$nonce = $request->attributes->get('csp_nonce');
// config/security-headers.php
return [
'hsts' => [
// Set SECURITY_HEADERS_HSTS=true in .env (only when fully on HTTPS)
'enabled' => env('SECURITY_HEADERS_HSTS', false),
'max_age' => 31536000,
'include_subdomains' => true,
],
'csp' => [
'enabled' => true,
'report_only' => false, // send Report-Only header instead of enforcing
'nonce_view_variable' => 'cspNonce',
'nonce_request_attribute' => 'csp_nonce',
'unsafe_eval' => true, // include 'unsafe-eval' in script-src
'unsafe_inline' => true, // include 'unsafe-inline' in style-src
// Extra sources merged into each directive
'script_src' => [], // appended to: 'self' 'nonce-...' (+ 'unsafe-eval' if enabled)
'style_src' => [], // appended to: 'self' (+ 'unsafe-inline' if enabled)
'img_src' => [], // appended to: 'self' data: blob:
'font_src' => [], // appended to: 'self' data:
'connect_src' => [], // appended to: 'self'
'frame_ancestors' => ["'self'"],
'form_action' => ["'self'"],
// Optional CSP reporting (omitted when null)
'report_uri' => null, // appended as `report-uri <uri>`
'report_to' => null, // appended as `report-to <group>`
],
// Set to null to omit the header entirely
'x_content_type_options' => 'nosniff',
'x_frame_options' => 'SAMEORIGIN',
'x_xss_protection' => '1; mode=block',
'referrer_policy' => 'strict-origin-when-cross-origin',
'permissions_policy' => 'geolocation=(), camera=(), microphone=(), payment=()',
'permitted_cross_domain_policies' => 'none', // X-Permitted-Cross-Domain-Policies (null/false to omit)
'vite' => [
'enabled' => true,
'dev_server' => 'http://127.0.0.1:5173',
],
];
By default, 'unsafe-eval' is included in script-src and 'unsafe-inline' is included in style-src for broad compatibility. You can disable these for stricter security:
'csp' => [
'unsafe_eval' => false, // removes 'unsafe-eval' from script-src
'unsafe_inline' => false, // removes 'unsafe-inline' from style-src
],
When unsafe_inline is disabled, all inline styles must use the CSP nonce. When unsafe_eval is disabled, eval() and related JavaScript features are blocked.
The following directives are always included and cannot be changed via config:
| Directive | Value | Purpose |
|---|---|---|
default-src | 'self' | Fallback for all resource types |
base-uri | 'self' | Prevents <base> tag hijacking |
object-src | 'none' | Blocks Flash/Java embeds |
'csp' => [
'script_src' => ['https://cdn.jsdelivr.net'],
],
'csp' => [
'font_src' => ['https://fonts.bunny.net', 'https://fonts.gstatic.com'],
'style_src' => ['https://fonts.bunny.net'],
],
'csp' => [
'connect_src' => ['wss://ws.example.com'],
],
'csp' => [
'form_action' => ["'self'", 'https://portal.example.com'],
],
SECURITY_HEADERS_HSTS=true
'csp' => [
'report_only' => true,
],
When report_only is true, the middleware sends Content-Security-Policy-Report-Only instead of Content-Security-Policy. This lets browsers report violations without blocking resources, which is useful when rolling out a new policy.
'csp' => [
'report_uri' => 'https://example.com/csp-report',
'report_to' => 'csp-endpoint',
],
When set, report-uri <uri> and/or report-to <group> are appended to the CSP header. Both are omitted by default.
'permitted_cross_domain_policies' => 'none', // default
Controls the X-Permitted-Cross-Domain-Policies header which restricts Adobe Flash/PDF cross-domain policy files. Set to null or false to omit the header.
use PhilipRehberger\SecurityHeaders\CspDirective;
$directive = CspDirective::ScriptSrc; // 'script-src'
$directive = CspDirective::from('style-src'); // CspDirective::StyleSrc
'x_xss_protection' => null,
| Class | Description |
|---|---|
SecurityHeaders | Middleware that injects all configured security headers into each response and generates a per-request CSP nonce |
| Enum | Description |
|---|---|
CspDirective | Backed string enum with cases for common CSP directives (DefaultSrc, ScriptSrc, StyleSrc, ImgSrc, FontSrc, ConnectSrc, MediaSrc, FrameSrc, BaseUri, FormAction, FrameAncestors) |
Auto-discovered via Laravel's package discovery. Registers the middleware and publishes the config file.
| Variable | Description |
|---|---|
$cspNonce | Per-request CSP nonce shared to all Blade views (name configurable via csp.nonce_view_variable) |
| Attribute | Description |
|---|---|
csp_nonce | Per-request CSP nonce accessible via $request->attributes->get('csp_nonce') |
composer install
vendor/bin/phpunit
vendor/bin/pint --test
vendor/bin/phpstan analyse
If you find this project useful: