In a Laravel app under a nonce-based Content-Security-Policy, how do you get Vite-generated tags to carry the nonce, and what does Vite::useCspNonce() cover?
answer
- call it in middleware before rendering
- Str::random(40) when no nonce passed
- Vite::cspNonce() for your own scripts
- singleton shares it with @vite
- preload and prefetch tags too
basics
~10 sCall Vite::useCspNonce() in a middleware before the view renders and send the returned nonce in the Content-Security-Policy header. Every tag the Vite class prints then carries nonce="…"; other inline scripts use Vite::cspNonce().
solid answer
~40 s`Vite::useCspNonce()` generates a nonce with `Str::random(40)` (or stores one you pass in), keeps it on the `Vite` singleton and returns it. Because the same instance renders `@vite` later in the request, a middleware that calls it before `$next($request)` and then adds `Content-Security-Policy: script-src 'nonce-…'` to the response is all the wiring needed. From then on the class adds `nonce` to the `<script type="module">` and stylesheet tags, the `modulepreload`/`preload` links, the inline `@viteReactRefresh` preamble and the inline script `Vite::prefetch()` prints. Anything you write yourself — an inline bootstrap script, a route-generator directive — reads the same value with `Vite::cspNonce()`, which returns `null` if no nonce was set.
code
php · 20 lines<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class AddContentSecurityPolicyHeaders
{
public function handle(Request $request, Closure $next): Response
{
$nonce = Vite::useCspNonce();
return $next($request)->withHeaders([
'Content-Security-Policy' => "script-src 'nonce-{$nonce}'",
]);
}
}go deeper
Recall that Vite::useCspNonce() in a middleware makes @vite add nonce attributes, and Vite::cspNonce() reads the value.
Explain why a middleware works: the Vite singleton carries the nonce from before $next to the render and back to the header.
Cover the tags outside Vite's reach, reuse an existing package's nonce, and keep nonce-protected pages out of full-page caches.
Plan the CSP rollout so the asset pipeline, third-party scripts and caching layers all agree on one per-response nonce.
## What problem the method solves A **nonce-based Content-Security-Policy** tells the browser to run only scripts (and, if you choose, styles) whose tag carries a `nonce` attribute matching a random value sent in the response header. The value must be fresh for every response. The difficulty in a Laravel app is that the `<script>` and `<link>` tags are printed by `@vite`, deep inside a Blade layout, while the header is set on the response object. Both need the same value. `Illuminate\Foundation\Vite` solves this by **holding the nonce itself**: | Method | Returns | What it does | |---|---|---| | `Vite::useCspNonce()` | the nonce string | generates one with `Str::random(40)` and stores it | | `Vite::useCspNonce($nonce)` | `$nonce` | stores a nonce you already have, e.g. from a CSP package | | `Vite::cspNonce()` | the string, or `null` | reads the stored value for your own tags | ## Why a middleware works The `Vite` class is registered as a **singleton** by Laravel's foundation service provider, and the `Vite` facade resolves that same instance. So a value stored by a middleware early in the request is visible when the view renders later in the same request. Under PHP-FPM every request starts with a fresh application, so each response gets its own nonce; the middleware also calls `useCspNonce()` on every request, generating a new one each time. ```php public function handle(Request $request, Closure $next): Response { Vite::useCspNonce(); return $next($request)->withHeaders([ 'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'", ]); } ``` The order matters: the nonce is set **before** `$next($request)` renders the view, and read **after** to build the header. Register the middleware on the `web` group in `bootstrap/app.php` with `$middleware->web(append: [...])`. ## Which tags get the nonce Once a nonce is set, the class adds `nonce="…"` to: - every `<script type="module" src="…">` for entries and, in dev mode, for `@vite/client`; - every `<link rel="stylesheet">` it prints; - every `<link rel="modulepreload">` and `<link rel="preload" as="style">` hint; - the inline module script printed by `@viteReactRefresh` during development; - the inline script printed when `Vite::prefetch()` is enabled. What it does **not** touch: - inline `<script>` blocks you write yourself — add `nonce="{{ Vite::cspNonce() }}"`; - third-party directives that print scripts, such as a route-name generator's `@routes` — pass the value in, e.g. `@routes(nonce: Vite::cspNonce())`; - the header itself — building a complete policy (which directives, `'strict-dynamic'`, reporting) is your middleware's job. ## Related tag controls on the same class - **Subresource Integrity**: if the manifest contains `integrity` hashes (added by a separate Vite plugin), the class prints `integrity` attributes automatically; `Vite::useIntegrityKey(false)` turns that off, and a string changes which manifest key is read. - **Arbitrary attributes**: `Vite::useScriptTagAttributes()`, `Vite::useStyleTagAttributes()` and `Vite::usePreloadTagAttributes()` take an array or a callback; returning `false` from the preload resolver suppresses that preload link. ## Development mode The same middleware keeps working while `npm run dev` runs: the `@vite/client` tag and the entry tags pointing at the dev server get the nonce too, and so does the `@viteReactRefresh` preamble. Your policy must then also allow the dev server's origin for scripts and its WebSocket for HMR, which is why many teams relax the policy locally and enforce the strict version only where assets come from a build. ## Checking the result 1. View source: every Vite-printed `<script>` and `<link>` should show the same `nonce` value as the response header. 2. Reload: the value must change on every response. 3. Open the browser console: CSP violations name the blocked element, which usually points at a hand-written script missing `Vite::cspNonce()`. ## Pitfalls interviewers probe 1. **Calling `useCspNonce()` after the response is built** — the tags have already rendered without it. 2. **Generating your own nonce for the header and never passing it to Vite** — the header and the tags disagree and every script is blocked; pass it with `Vite::useCspNonce($nonce)`. 3. **Forgetting hand-written inline scripts** — they are outside the class's reach. 4. **Caching the whole HTML response** — a cached page replays one nonce to every visitor, which defeats its purpose; nonce-protected pages must be rendered per response. 5. **Assuming `cspNonce()` always returns a string** — it is `null` until a nonce is set, so templates that print it unconditionally emit an empty attribute.
- Your app already uses a CSP package that generates its own nonce. How do you keep Vite's tags in step with it?Pass the package's value into the Vite class in a middleware that runs before the view renders: `Vite::useCspNonce($nonce)`. The method stores the given string instead of generating one, so the tags and the package's header carry the same nonce.
- Does the inline script that Vite::prefetch() adds need special handling under the policy?No. When prefetching is enabled, the class prints its inline loader script with the stored nonce attribute, so the same `useCspNonce()` call covers it. Only scripts you write yourself need `Vite::cspNonce()` added by hand.
saying these in an interview costs you the question
- useCspNonce() must be called inside the Blade view after @vite
- Vite::cspNonce() generates a new nonce on every call
- Laravel adds a Content-Security-Policy header automatically once useCspNonce() runs
- The nonce also gets added to inline scripts you write yourself
- A fully cached HTML page can safely reuse one nonce for every visitor