paulund
#laravel #security #best-practices

Security Headers

HTTP security headers tell the browser how to handle your content. They are low-effort and high-impact — a couple of hours of work that closes off a whole class of browser-based attacks including clickjacking, cross-site scripting, and content injection.

Key Headers

Content-Security-Policy (CSP)

CSP tells the browser which sources of scripts, styles, images, and other resources are legitimate. It prevents injected scripts from running even if an attacker manages to insert them into your HTML.

A strict example:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';

Start with Content-Security-Policy-Report-Only to collect violations without blocking anything, then tighten the policy once you understand your application's legitimate sources.

X-Frame-Options

Prevents your pages from being embedded in an <iframe> on another domain, protecting against clickjacking:

X-Frame-Options: SAMEORIGIN

X-Content-Type-Options

Stops browsers from guessing the MIME type of a response. Without this, a browser might execute a .txt file as JavaScript if the content looks like a script:

X-Content-Type-Options: nosniff

Referrer-Policy

Controls how much referrer information is sent when navigating away from your site. strict-origin-when-cross-origin is a sensible default:

Referrer-Policy: strict-origin-when-cross-origin

Permissions-Policy

Restricts which browser features (camera, microphone, geolocation) the page can access. Deny everything your application does not need:

Permissions-Policy: camera=(), microphone=(), geolocation=()

Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS for all future requests to your domain. Only add this once your site is fully on HTTPS:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Adding Headers in Laravel Middleware

Create a middleware class to attach security headers to every response:

php artisan make:middleware SecurityHeaders
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
        $response->headers->set(
            'Content-Security-Policy',
            "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
        );

        return $response;
    }
}

Register it globally in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})

That's all you need in Laravel 12. The bootstrap/app.php file is the single place to configure middleware for your application.

Using a Package

The bepsvpt/secure-headers package takes a configuration-driven approach with sensible defaults:

composer require bepsvpt/secure-headers
php artisan vendor:publish --tag=secure-headers

Configure headers in config/secure-headers.php and add the middleware to your stack. The package handles edge cases like removing headers that should not be present in certain response types.

Testing Your Headers

After deploying, check your headers with:

Both tools give an A-F grade and explain what each header does and why it matters.

Tips

  • Start with report-only CSP and monitor violations before enforcing.
  • Do not add Strict-Transport-Security until you are fully committed to HTTPS. A high max-age is difficult to undo.
  • If you use a CDN like Cloudflare, headers such as HSTS and CSP can be configured at the CDN layer rather than in Laravel.
  • Review your CSP whenever you add a new third-party script or font. CDN sources need to be explicitly allowed.