
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Browser-side attacks remain the most common entry point for compromising web applications, and implementing a robust Content Security Policy CSP for Laravel Apps is your strongest defense against Cross-Site Scripting (XSS) and data injection. While Laravel provides excellent server-side validation, it cannot prevent malicious scripts injected via compromised third-party libraries or user-generated content from executing in the browser. This guide walks you through configuring, testing, and enforcing CSP headers specifically tailored for Laravel’s Blade templating and asset pipeline without breaking your production environment.
spatie/laravel-csp package, configure a custom policy class with strict script/style directives using nonces, enable report-only mode first to capture violations via an internal endpoint, and only switch to enforcement after validating logs for false positives.How do you configure Content Security Policy CSP for Laravel Apps safely?
The safest approach to deploying CSP in any PHP framework is to treat it as an observability problem before treating it as a security control. A common mistake I see teams make—especially when rushing to satisfy audit requirements like SOC 2 or ISO 27001—is flipping CSP to enforcement mode immediately. This inevitably breaks payment gateways, analytics, or embedded widgets because modern web apps have complex dependency graphs that are rarely fully documented. Instead, follow this proven sequence: define a baseline policy, deploy in report-only mode, collect violation data for at least two weeks, refine allowlists based on real traffic, and only then enforce.
Laravel does not include CSP middleware out of the box. While you could set headers directly in Nginx or Apache, doing so prevents you from using dynamic nonces—a critical requirement for allowing inline scripts securely. The community-standard solution is spatie/laravel-csp, which integrates deeply with Blade and generates per-request nonces automatically. Install it via Composer and publish the configuration:
composer require spatie/laravel-csp
php artisan vendor:publish --tag=csp-config This creates config/csp.php and a default policy class. Never edit the published config to add domains directly; instead, create a custom policy class that extends Spatie’s base policy. This keeps your rules version-controlled, testable, and reviewable in pull requests—a practice aligned with shifting security left in CI/CD.
Create a Custom Policy Class
Generate a new policy tailored to your application’s actual dependencies. Avoid copying generic examples; every directive should map to a verified business need:
<?php
namespace App\Csp;
use Spatie\Csp\Policies\Policy;
use Spatie\Csp\Directives;
class LaravelAppPolicy extends Policy
{
public function configure()
{
$this
->addDirective(Directives::BASE, "'self'")
->addDirective(Directives::SCRIPT, [
"'self'",
"'nonce-{nonce}'", // Auto-replaced by middleware
'https://js.stripe.com',
'https://cdn.jsdelivr.net',
])
->addDirective(Directives::STYLE, [
"'self'",
"'nonce-{nonce}'",
'https://fonts.googleapis.com',
])
->addDirective(Directives::FONT, [
"'self'",
'https://fonts.gstatic.com',
])
->addDirective(Directives::IMG, [
"'self'",
'data:',
'https://*.amazonaws.com', // S3 uploads
])
->addDirective(Directives::CONNECT, [
"'self'",
'https://api.stripe.com',
'https://sentry.io',
])
->addNonceForDirective(Directives::SCRIPT)
->addNonceForDirective(Directives::STYLE);
}
} Register this policy in config/csp.php under the policies key and ensure the middleware is applied globally or to specific route groups. For applications serving both a marketing site and an authenticated app, consider separate policies—the admin panel may need stricter controls than the public-facing pages.
Why are nonces essential for Laravel CSP instead of hashes?
You might wonder why we use nonces rather than SHA-256 hashes for inline scripts. Hashes work well for static content but become unmanageable in Laravel because Blade templates often contain dynamic data embedded in JavaScript (e.g., @json($user)). Every change to that data invalidates the hash, forcing you to recompute and redeploy. Nonces solve this by generating a unique cryptographic token per HTTP response that applies to all inline blocks marked with that nonce.
Spatie’s package handles nonce generation and injection automatically when you use its Blade directives. Replace raw <script> tags with:
@cspScriptTag
window.APP_CONFIG = @json($config);
@endcspScriptTag
@cspStyleTag
.dynamic-theme { color: {{ $themeColor }}; }
@endcspStyleTag If you’re using Vite (standard in Laravel 11+), the build tool automatically adds nonces to generated asset tags when the CSP middleware is active. Verify this by inspecting the rendered HTML in production—you should see nonce="..." attributes on every <script> and <link> tag. Missing nonces indicate either misconfigured middleware or cached views; run php artisan view:clear and check your production deployment checklist for cache-busting steps.
How do you monitor CSP violations without disrupting users?
Enforcement without monitoring is operational negligence. Before switching from report-only to enforced mode, you must establish a feedback loop. Configure your policy to send violation reports to an internal endpoint rather than an external service—this avoids leaking sensitive URL parameters or page structure to third parties and satisfies data residency concerns relevant to Nepali fintech or healthcare clients.
Add a dedicated route and controller to ingest CSP reports:
// routes/web.php
Route::post('/csp-report', [CspReportController::class, 'store'])
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
// app/Http/Controllers/CspReportController.php
public function store(Request $request)
{
Log::channel('csp')->warning('CSP Violation', [
'document-uri' => $request->input('document-uri'),
'violated-directive' => $request->input('violated-directive'),
'blocked-uri' => $request->input('blocked-uri'),
'source-file' => $request->input('source-file'),
'user-agent' => $request->userAgent(),
]);
return response()->noContent();
} Create a dedicated log channel in config/logging.php to isolate CSP violations from application logs. In production, ship these to your centralized logging stack—whether that’s ELK, Grafana Loki, or CloudWatch. Set up alerts for spikes in specific directives; a sudden surge in script-src violations after a deploy usually indicates a missing nonce or a newly introduced third-party library. During my work helping teams achieve SOC 2 compliance, automated CSP violation monitoring has been invaluable evidence of continuous security control effectiveness during audits.
What are the most common CSP pitfalls in Laravel production environments?
Even experienced teams stumble on these issues when hardening Laravel apps. Understanding them upfront saves hours of debugging:
- Overusing
'unsafe-inline': This defeats the purpose of CSP. If you’re tempted to add it because “nothing works,” you’ve skipped the reporting phase. Go back, collect violations, and fix the root cause. - Ignoring CDN subresource integrity: When allowing
cdn.jsdelivr.net, pair it with SRI hashes in your HTML. CSP allows the domain, but SRI ensures the file hasn’t been tampered with—a defense-in-depth practice I recommend for all security-hardened deployments. - Forgetting WebSocket connections: If you use Laravel Reverb or Pusher, add
wss://schemes toconnect-src. Browsers block WS/WSS upgrades silently unless explicitly allowed. - Caching stale policies: Always set
Cache-Control: no-storeon CSP headers during tuning. Browser caches can persist old report-only policies for hours, making debugging impossible. - Mixing meta tags and headers: Never define CSP in both
<meta>and HTTP headers. Headers take precedence, and meta tags don’t supportframe-ancestorsor reporting. Stick to middleware-generated headers exclusively.
| Directive | Risky Value | Secure Alternative | Laravel Context |
|---|---|---|---|
script-src | 'unsafe-inline' | 'nonce-{nonce}' | Use @cspScriptTag for all inline JS |
style-src | 'unsafe-inline' | 'nonce-{nonce}' + allowlisted fonts | Tailwind JIT may need nonce; prebuilt CSS doesn’t |
img-src | * | 'self' data: https://*.yourdomain.com | Restrict S3/R2 buckets to known prefixes |
connect-src | https: | Explicit API domains + WSS endpoints | Include Reverb/Pusher/Sentry URLs |
frame-ancestors | 'none' (default) | 'self' if embedding internally | Required for admin panels in iframes |
When should you enforce CSP in production Laravel deployments?
Switch to enforcement only after meeting three criteria: you’ve collected at least 14 days of violation reports covering peak traffic patterns, all high-frequency violations have been resolved or explicitly accepted as risk, and you have automated alerting configured for new violation types. In practice, this means your staging environment should mirror production CSP behavior exactly—including the same third-party integrations and CDN configurations.
Update your policy class to remove report-only mode and add the upgrade-insecure-requests directive if you haven’t already enforced HTTPS everywhere. Test thoroughly across browsers; Safari historically has quirks with nonce handling that require fallback strategies. Document your final policy in your security runbook and link it to your OWASP Top 10 mitigation documentation for audit readiness.
Next Steps for Securing Your Laravel Application
Implementing Content Security Policy CSP for Laravel Apps is not a one-time task—it’s an ongoing process of refinement as your application evolves. Start today by installing the Spatie package in report-only mode, instrumenting violation logging, and reviewing your current inline script usage. Within two weeks, you’ll have actionable data to build a strict, enforceable policy that genuinely protects your users without degrading functionality. If you need help designing a CSP strategy that aligns with your compliance requirements or infrastructure constraints, reach out to discuss your specific setup.