How to create a Content Security Policy in Laravel Applications

The Laravel package for CSP (Content Security Policy) supports basic content security in your application. However, you can create and add your own content security policies when using more external resources. 

Let’s go through the steps to create a Content Security Policy that allows loading a given set of assets and scripts on your page and blocks if the browser attempts to load any other resource. 

Content Security Policy in Laravel

Consider the basic blade file for a page. 

<html>

   <head>

       <title>Custom CSP</title>

       {{-- Load Vue.js from the CDN --}}

       <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

       {{-- Load some JS scripts from our domain --}}

       <script src="{{ asset('js/app.js') }}"></script>

       {{-- Load Bootstrap 5 CSS --}}

<link href="https://cdn.jsdelivr.net/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"   integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EG" crossorigin="anonymous">

{{-- Load a CSS file from our own domain --}}

<link rel="stylesheet" href="{{ asset('css/app.css') }}">

</head>

   <body>

       <h1>Csp Header</h1>

       <img src="{{ asset('img/image.png') }}" alt="CSP image">

       <img src="https://scratchcoding.dev/img/logotype.min.svg" alt="App Logo">

       {{-- Define some JS directly in our HTML. --}}

       <script>

           console.log('Loaded inline script!');

       </script>

       {{-- JS script injected by another script! --}}

       <script>

           console.log('Injected malicious script!');

       </script>

   </body>

</html>

The basic CSP policies as shown above, are auto-added by Laravel once you install the laravel-csp package. 

Next, let’s create our custom policy class. 

namespace App\Support\Csp\Policies;

use Spatie\Csp\Policies\Basic;

class CustomPolicy extends Basic

{

   public function configure()

   {

       parent::configure();

   }

}

Here, you can add your custom policies code in the configure() function. We will add a few directives to the function. 

public function configure()

   {

       parent::configure();

       $this->addDirective(Directive::SCRIPT, ['https://unpkg.com/vue@3/'])

           ->addDirective(Directive::STYLE, ['https://cdn.jsdelivr.net/bootstrap@5.3.0/'])

           ->addDirective(Directive::IMG, 'https://scratchcoding.dev);

   }

This will create a CSP header as shown below. 

base-uri ‘self’;connect-src ‘self’;default-src ‘self’;form-action ‘self’;img-src ‘self’;media-src ‘self’;object-src ‘none’;script-src ‘self’ ‘nonce-3fvDDho6nNJ3xXPcK3VMsgBWjVTJzijk’ https://unpkg.com/vue@3/;style-src ‘self’ ‘nonce-3fvDDho6nNJ3xXPcK3VMsgBWjVTJzijk’ https://cdn.jsdelivr.net/bootstrap@5.3.0/

This defines the following:

  • JS files from the URL starting with https://unpkg.com/vue@3/ will be loaded in the app. 
  • CSS files from the URL starting https://cdn.jsdelivr.net/bootstrap@5.3.0 will be loaded in the app, and 
  • Image assets starting from scratchcoding.dev will be loaded in the app.

Moreover, support for inline JavaScript, and other assets and scripts from the same domain are added by our basic CSP by default.

We can make these more strict rules to enhance security of applications. Suppose a malicious file reaches the URL https://unpkg.com/vue@3 and uploads a file there. Allowing this URL provides permission for all files under this URL to reach your application. 

Adding more specific URLs improves the security of a page. You can directly pass exact URLs of the files, assets and resources to load. 

Use of Nonces in CSP

Alongside the external scripts, we should also load the inline JavaScript on our web app. Nonces help run inline JavaScript added to a web page. These are random strings generated for each request. It is then added to a CSP header, using the csp_nonce() helper as shown below.

<html>

       <!-- ... -->

       {{-- Define some JS directly in our HTML. --}}

       <script nonce="{{ csp_nonce() }}">

           console.log('Loaded inline script!');

       </script>

       {{-- JS script which we didn't write ourselves and was injected by another script! --}}

       <script>

           console.log(‘Malicious script!');

       </script>

   </body>

</html>

Use of Meta Tag 

CSP for a page can also be added as a Meta tag in the head section. 

<head>

   @cspMetaTag(App\Support\Csp\Policies\CustomPolicy::class)

 </head>

The Meta Tag method should be used only when the HTTP header solution does not work. As this solution distinguishes the CSP rules from other metadata.