Content Security Policy (CSP) is a crucial security feature that helps protect your web applications from various attacks, particularly Cross-Site Scripting (XSS). In this guide, we'll dive deep into implementing CSP in Laravel applications, exploring both the basics and advanced techniques.
What is Content Security Policy?
Content Security Policy is an HTTP header that tells browsers which resources are allowed to load and execute in your web application. It's a powerful tool for preventing:
- Cross-Site Scripting (XSS) attacks
- Clickjacking
- Malicious code injection
- Unwanted resource loading
Common CSP Implementation Methods in Laravel
Method 1: Using .htaccess
(Not Recommended)
While it's possible to implement CSP through .htaccess
, this approach has several limitations:
Header set Content-Security-Policy "default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';"
Drawbacks:
- Server-specific (only works with Apache)
- Harder to maintain
- Can't dynamically adjust policies
- No access to Laravel's features
Method 2: Laravel Middleware (Recommended)
The preferred approach is using Laravel middleware. Here's a step-by-step implementation:
- Create a dedicated middleware:
php artisan make:middleware ContentSecurityPolicy
- Implement the middleware logic:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ContentSecurityPolicy
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$csp = $this->buildCspPolicy();
$response->headers->set('Content-Security-Policy', $csp);
return $response;
}
private function buildCspPolicy(): string
{
return "default-src 'self'; " .
"img-src 'self' data: https://trusted-image-cdn.com; " .
"style-src 'self' 'unsafe-inline' https://fonts.bunny.net; " .
"font-src 'self' https://fonts.bunny.net; " .
"script-src 'self' 'unsafe-eval' 'unsafe-inline'; " .
"object-src 'none';";
}
}
- Register in
app/Http/Kernel.php
:
protected $middleware = [
// ... other middleware
\App\Http\Middleware\ContentSecurityPolicy::class,
];
Advanced CSP Implementation
Environment-Specific Policies
Different environments often require different CSP configurations:
private function buildCspPolicy(): string
{
if (app()->environment('production')) {
return $this->getProductionPolicy();
}
return $this->getDevelopmentPolicy();
}
private function getProductionPolicy(): string
{
return "default-src 'self'; " .
"script-src 'self' 'nonce-" . $this->generateNonce() . "'; " .
"style-src 'self';";
}
private function getDevelopmentPolicy(): string
{
return "default-src 'self'; " .
"script-src 'self' 'unsafe-eval' 'unsafe-inline'; " .
"style-src 'self' 'unsafe-inline';";
}
Using Nonces for Inline Scripts
Instead of using unsafe-inline
, you can generate nonces for better security:
class ContentSecurityPolicy
{
private function generateNonce(): string
{
$nonce = bin2hex(random_bytes(16));
app()->singleton('csp-nonce', fn() => $nonce);
return $nonce;
}
}
In your Blade views:
<script nonce="{{ app('csp-nonce') }}">
// Your inline JavaScript
</script>
CSP Reporting
Implement CSP reporting to monitor violations:
class ContentSecurityPolicy
{
private function buildCspPolicy(): string
{
$policy = // ... your regular policy ...;
if (app()->environment('production')) {
$policy .= "report-uri /csp-report;";
}
return $policy;
}
}
Create a route to handle reports:
Route::post('csp-report', function (Request $request) {
Log::channel('csp')->info('CSP Violation', $request->all());
});
Best Practices
- Start Strict: Begin with a strict policy and loosen only as needed:
"default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';"
-
Avoid Unsafe Directives: Minimize use of
unsafe-inline
andunsafe-eval
:
// Instead of unsafe-inline, use nonces
"script-src 'self' 'nonce-{random-nonce}';"
- Use Report-Only Mode: Test policies before enforcement:
$response->headers->set('Content-Security-Policy-Report-Only', $csp);
- Regular Audits: Regularly review and update your CSP:
- Monitor CSP violation reports
- Remove unused directives
- Update policies when adding new resources
Troubleshooting Common Issues
1. Inline Scripts Not Working
Problem:
<script>
alert('This won't work!');
</script>
Solution:
<script nonce="{{ app('csp-nonce') }}">
alert('This will work!');
</script>
2. Third-Party Resources Blocked
Problem:
"default-src 'self';" // Blocks external resources
Solution:
"default-src 'self'; script-src 'self' https://trusted-cdn.com;"
Laravel Telescope & Laravel Horizon
For Laravel Telescope & Laravel Horizon, I've added specifically for them in the middleware:
// Special CSP for Horizon
if ($request->is('horizon*')) {
$csp = "default-src 'self'; ".
"img-src 'self' data: https://ui-avatars.com; ".
"style-src 'self' 'unsafe-inline' https://fonts.bunny.net; ".
"font-src 'self' https://fonts.bunny.net; ".
"script-src 'self' 'unsafe-inline'; ".
"object-src 'none';";
}
// Special CSP for Telescope
if ($request->is('telescope*')) {
$csp = "default-src 'self'; ".
"img-src 'self' data: https://ui-avatars.com; ".
"style-src 'self' 'unsafe-inline' https://fonts.bunny.net; ".
"font-src 'self' https://fonts.bunny.net; ".
"script-src 'self' 'unsafe-inline'; ".
"object-src 'none';";
}
Conclusion
Implementing CSP in Laravel through middleware provides a robust, maintainable solution for securing your web application. While the initial setup might require some effort, the security benefits far outweigh the implementation costs. Remember to:
- Use middleware instead of .htaccess
- Implement environment-specific policies
- Use nonces or hashes instead of unsafe-inline
- Monitor CSP violations
- Regularly audit and update your policies
By following these guidelines, you'll significantly enhance your application's security against XSS and other common web vulnerabilities.
Resources
Photo by Willian Justen de Vasconcellos on Unsplash
Top comments (1)
good info and informative steps