Content Security Policy (CSP) is a critical security feature that helps prevent cross-site scripting (XSS) attacks by controlling which resources can be loaded by a web page. In this guide, we’ll create a Laravel middleware to enforce CSP headers dynamically, ensuring security while allowing scripts and styles to be executed safely. When it comes to implementing a CSP in Laravel, the package spatie/laravel-csp is almost always recommended. Below, we show how to achieve this without using an additional package.
We will create a middleware called AddContentSecurityPolicyHeaders.php that dynamically generates a nonce for each request. This nonce will be shared with Blade templates and used in inline scripts and styles.
Create a new middleware file in app/Http/Middleware/
php artisan make:middleware AddContentSecurityPolicyHeaders
and add the following code:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
class AddContentSecurityPolicyHeaders
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// Generate CSP nonce using Laravel Vite
Vite::useCspNonce();
$cspNonce = Vite::cspNonce();
// Share nonce with Blade views
View::share('cspNonce', $cspNonce);
// Define Content Security Policy header
$cspHeader = "script-src 'nonce-{$cspNonce}' ; " .
"object-src 'none'; " .
"base-uri 'self'; " .
"style-src 'nonce-{$cspNonce}' ; ";
// Apply CSP headers to the response
return $next($request)->withHeaders([
'Content-Security-Policy' => $cspHeader,
]);
}
}
To ensure that the middleware is applied globally, register it in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->prepend(
AddContentSecurityPolicyHeaders::class
);
})
This ensures that every request includes the appropriate CSP headers.
To allow inline scripts and styles while maintaining security, use the CSP nonce in your Blade views:
<script nonce="{{ $cspNonce }}">
console.log("Secure script executed!");
</script>
Similarly, you can use the nonce for inline styles:
<style nonce="{{ $cspNonce }}">
body { background-color: #f0f0f0; }
</style>
Implementing a strong CSP policy is an essential step in securing your Laravel application against XSS attacks. By dynamically generating a nonce and enforcing CSP headers, you ensure that only trusted scripts and styles are executed.
Here is a code example for the case that you want to disable CSP locally and, for example, use a different CSP definition in the admin area than in the public section of your website. It also shows how you can allow certain external sources.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
class AddContentSecurityPolicyHeaders
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
Vite::useCspNonce();
$cspNonce = Vite::cspNonce();
View::share('cspNonce', $cspNonce);
if (app()->environment('local') || $request->is('admin/log-viewer')) {
$cspHeader = "";
} elseif ($request->is('admin*')) {
$cspHeader = "script-src 'nonce-{$cspNonce}' 'strict-dynamic'; " .
"object-src 'none'; " .
"base-uri 'self'; " .
"form-action 'self'";
} else {
$cspHeader = "script-src 'nonce-{$cspNonce}' https://www.googletagmanager.com ; " .
"object-src 'none'; " .
"base-uri 'self'; " .
"style-src 'self' 'nonce-{$cspNonce}' https://cdnjs.cloudflare.com https://fonts.googleapis.com;" .
"form-action 'self';";
}
return $next($request)->withHeaders([
'Content-Security-Policy' => $cspHeader,
]);
}
}
For testing, we recommend the CSP Evaluator (also available as a Chrome Extension): csp-evaluator.withgoogle.com