philiprehberger / laravel-security-headers
Laravel middleware for comprehensive security headers including CSP with nonce support, HSTS, and Permissions-Policy.
Package info
github.com/philiprehberger/laravel-security-headers
pkg:composer/philiprehberger/laravel-security-headers
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpstan/extension-installer: ^1.4
- phpunit/phpunit: ^11.0
README
A Laravel middleware package that adds a comprehensive set of HTTP security headers to every response, including a per-request CSP nonce, HSTS, and Permissions-Policy.
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| Laravel | 11 or 12 |
Features
- Per-request CSP nonce — generated automatically and shared with all Blade views
- Content Security Policy built entirely from config arrays, no code changes required
- Configurable HSTS with
max_ageandincludeSubDomains X-Content-Type-Options,X-Frame-Options,X-XSS-Protection,Referrer-Policy,Permissions-Policy- Vite dev-server auto-detection adds the HMR origin and WebSocket URLs to the CSP when
APP_ENV=local - Any header can be suppressed by setting its config value to
null - Laravel 11 and 12 support, PHP 8.2+
Installation
composer require philiprehberger/laravel-security-headers
Laravel auto-discovery registers the service provider automatically.
Publishing the Config
php artisan vendor:publish --tag=security-headers-config
This copies config/security-headers.php into your application's config/ directory.
Registering the Middleware
Laravel 11+ (bootstrap/app.php)
use PhilipRehberger\SecurityHeaders\SecurityHeaders; ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ SecurityHeaders::class, ]); })
Laravel 10 and earlier (app/Http/Kernel.php)
protected $middlewareGroups = [ 'web' => [ // ... \PhilipRehberger\SecurityHeaders\SecurityHeaders::class, ], ];
Using the CSP Nonce in Blade
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>
Accessing the Nonce in PHP
$nonce = $request->attributes->get('csp_nonce');
Configuration Reference
// 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, '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'"], ], // 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=()', 'vite' => [ 'enabled' => true, 'dev_server' => 'http://127.0.0.1:5173', ], ];
Hardening the CSP
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.
Hardcoded CSP Directives
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 |
Customization Examples
Allow an external CDN for scripts
'csp' => [ 'script_src' => ['https://cdn.jsdelivr.net'], ],
Allow external font providers
'csp' => [ 'font_src' => ['https://fonts.bunny.net', 'https://fonts.gstatic.com'], 'style_src' => ['https://fonts.bunny.net'], ],
Allow WebSocket connections to a production server
'csp' => [ 'connect_src' => ['wss://ws.example.com'], ],
Allow forms to post to a subdomain
'csp' => [ 'form_action' => ["'self'", 'https://portal.example.com'], ],
Enable HSTS in production via environment variable
SECURITY_HEADERS_HSTS=true
Remove a header you do not need
'x_xss_protection' => null,
API
Middleware
| Class | Description |
|---|---|
SecurityHeaders |
Middleware that injects all configured security headers into each response and generates a per-request CSP nonce |
Service Provider
Auto-discovered via Laravel's package discovery. Registers the middleware and publishes the config file.
Blade Variable
| Variable | Description |
|---|---|
$cspNonce |
Per-request CSP nonce shared to all Blade views (name configurable via csp.nonce_view_variable) |
Request Attribute
| Attribute | Description |
|---|---|
csp_nonce |
Per-request CSP nonce accessible via $request->attributes->get('csp_nonce') |
Development
composer install vendor/bin/phpunit vendor/bin/pint --test vendor/bin/phpstan analyse
License
MIT