Certainly! Setting a strict but functional Content-Security-Policy (CSP) in a Laravel app—especially when using Vite and some external resources—can be tricky. The key is to balance security with the real needs of your app.
General Advice:
- Start strict (
'self'everywhere), then loosen only what you need. - Avoid
'unsafe-inline'and'unsafe-eval'in production if possible. - For Vite in development, you need to allow its dev server and websockets.
- For production, only allow the origins you actually use (e.g., Google Fonts).
Example Middleware for CSP in Laravel
Here’s a refined middleware example for both development (Vite) and production:
public function handle(Request $request, Closure $next)
{
$response = $next($request);
if (app()->environment('local')) {
// Allow Vite dev server and hot module reload websocket
$viteDevSrc = "http://localhost:5173 http://127.0.0.1:5173 ws://localhost:5173 ws://127.0.0.1:5173";
$csp = "default-src 'self'; "
. "script-src 'self' {$viteDevSrc}; "
. "style-src 'self' 'unsafe-inline' {$viteDevSrc} https://fonts.googleapis.com; "
. "img-src 'self' data: blob:; "
. "font-src 'self' data: https://fonts.gstatic.com; "
. "connect-src 'self' {$viteDevSrc}; "
. "frame-src 'none'; "
. "object-src 'none'; "
. "base-uri 'self'; "
. "form-action 'self'; "
. "upgrade-insecure-requests;";
} else {
// Only allow what you actually use in production!
$csp = "default-src 'self'; "
. "script-src 'self'; "
. "style-src 'self' https://fonts.googleapis.com; "
. "img-src 'self' data: blob:; "
. "font-src 'self' https://fonts.gstatic.com; "
. "connect-src 'self'; "
. "frame-src 'none'; "
. "object-src 'none'; "
. "base-uri 'self'; "
. "form-action 'self'; "
. "upgrade-insecure-requests;";
}
$response->headers->set('Content-Security-Policy', $csp);
// Other security headers (as you had)
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
$response->headers->set('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', '0');
$response->headers->set('Cross-Origin-Resource-Policy', 'same-origin');
$response->headers->set('Cross-Origin-Opener-Policy', 'same-origin');
return $response;
}
Key Points
- Development:
- Allow Vite dev server (
localhost:5173) for scripts, styles, and websocket connections. 'unsafe-inline'for styles is usually needed in dev, but try to avoid it in production.
- Allow Vite dev server (
- Production:
- Only allow origins you actually use (e.g., Google Fonts).
- Remove
'unsafe-inline'fromstyle-srcif possible. If you use inline styles, consider using a nonce or hash. - If you use any third-party scripts or APIs, add their domains to the appropriate directives.
- Testing:
- Use browser dev tools (Console > Security) to see what’s being blocked.
- Adjust CSP as needed, but always start strict.
Troubleshooting
- If something breaks, check the browser console for CSP errors. It will tell you what was blocked and why.
- Gradually add only the minimum required sources to each directive.
Tools
Summary:
Your approach is good, but you need to loosen the policy only as much as your app requires. Start strict, check what’s blocked, and add only what’s necessary. For Vite, make sure to allow its dev server in development. In production, keep it as tight as possible.
Let me know if you need help with a specific CSP error message!