vincent15000's avatar

InertiaJS and Security Headers ?

Hello,

How is it possible to add CPS headers with InertiaJS ?

I have tried this via a middleware.

public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);

    $csp = implode('; ', [
        "default-src 'self'",
        "img-src 'self' data:",
        "script-src 'self' 'unsafe-inline'",
        "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/css2",
        "font-src 'self' https://fonts.gstatic.com/s/figtree/v9/",
    ]);

    if (config('app.env') !== 'local') {
        $response->headers->set('Content-Security-Policy', $csp);
    }

    return $response;
}

But unsafe-inline is not secure.

I wanted to try with a nonce, but it seems to be quite complex to do that with InertiaJS.

Any suggestion ?

Thanks for your help.

V

0 likes
8 replies
aleahy's avatar

In addition to what you've done, you need to tell Vite to add the nonce in the middleware via Vite::useCspNonce().

This is from the laravel docs:

        Vite::useCspNonce();
 
        return $next($request)->withHeaders([
            'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'",
        ]);

For convenience, it's also worth using Spatie's package: https://github.com/spatie/laravel-csp

1 like
vincent15000's avatar

Oh thank you I didn't even know that it was specified in the Laravel docs.

I have added the nonce like mentioned in the documentation, but I get this error.

Executing inline script violates the following Content Security Policy directive 'script-src 'self' 'nonce-3E6viSgDnRw26VLveosL5pgWUz8UlQRzLZRPS736''. Either the 'unsafe-inline' keyword, a hash ('sha256-uZu30YR5Q7xMFJcsmo+SoZwQUlCrvcfNNMjBIlUPasE='), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked.

Any idea what I can do now ?

Jsanwo64's avatar

Middleware (nonce generation)

public function handle(Request $request, Closure $next): Response
{
    $nonce = base64_encode(random_bytes(16));

    app()->instance('csp_nonce', $nonce);

    $response = $next($request);

    $csp = implode('; ', [
        "default-src 'self'",
        "img-src 'self' data:",
        "script-src 'self' 'nonce-$nonce'",
        "style-src 'self' 'nonce-$nonce' https://fonts.googleapis.com",
        "font-src 'self' https://fonts.gstatic.com",
    ]);

    $response->headers->set('Content-Security-Policy', $csp);

    return $response;
}

Pass nonce to Blade (Inertia root template) In your app.blade.php (or root layout):

@php($nonce = app('csp_nonce'))

Then:

<script nonce="{{ $nonce }}">
    window.__CSP_NONCE__ = "{{ $nonce }}";
</script>
1 like
vincent15000's avatar

@jsanwo64 @aleahy

I still get an error, probably the script concerned by this error doesn't have any nonce.

How can I be sure that all needed scripts, also the scripts that are dynamically injected by Vue, have a nonce ?

aleahy's avatar

I think what @jsanwo64 was suggesting was that any scripts tags in your vue components could reference the nonce from the window variable.

<template>
  <script :nonce="nonceValue">
    console.log('Safe inline script');
  </script>
</template>

<script setup>
const nonceValue = window. __CSP_NONCE__;
</script>

But that is assuming you have access to whatever is injecting the script.

Have you made sure the issue still exists when you run npm run build? All the vue stuff should be compiled by vite and it should put the nonce on the script for that....

1 like
Jsanwo64's avatar

Can you paste what you have in the layout (app.blade.php)

Please or to participate in this conversation.