philiprehberger/laravel-security-headers

Laravel middleware for comprehensive security headers including CSP with nonce support, HSTS, and Permissions-Policy

Maintainers

Package info

github.com/philiprehberger/laravel-security-headers

pkg:composer/philiprehberger/laravel-security-headers

Fund package maintenance!

philiprehberger

Statistics

Installs: 44

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.3.0 2026-04-07 03:42 UTC

README

Tests Latest Version on Packagist Last updated

Laravel middleware for comprehensive security headers including CSP with nonce support, HSTS, and Permissions-Policy.

Requirements

  • PHP 8.2+
  • Laravel 11 or 12

Installation

composer require philiprehberger/laravel-security-headers

Laravel auto-discovery registers the service provider automatically.

Usage

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,
        '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',
    ],

];

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

Enable Report-Only mode to test your CSP without enforcing it

'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.

Send CSP violation reports to an endpoint

'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.

Configure X-Permitted-Cross-Domain-Policies

'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 the CspDirective enum for type-safe directive names

use PhilipRehberger\SecurityHeaders\CspDirective;

$directive = CspDirective::ScriptSrc; // 'script-src'
$directive = CspDirective::from('style-src'); // CspDirective::StyleSrc

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

Enums

Enum Description
CspDirective Backed string enum with cases for common CSP directives (DefaultSrc, ScriptSrc, StyleSrc, ImgSrc, FontSrc, ConnectSrc, MediaSrc, FrameSrc, BaseUri, FormAction, FrameAncestors)

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

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT