★ Using Content Security Policy headers in a Laravel app
Tags: laravel, php, security
Published: 2020-01-02 22:59:14

---
title: ★ Using Content Security Policy headers in a Laravel app
published: true
date: 2018-02-20 19:42:50 UTC
tags: laravel, php, security
canonical_url: https://freek.dev/982-using-content-security-policy-headers-in-a-laravel-app
---

By default all scripts on a webpage are allowed to send and fetch data from and to any site they want. If you think about it, that's kinda scary. Imagine that one of your JavaScript dependencies would send all keystrokes, including passwords, to a third party website. That would be pretty bad.

In this blogpost I'd like to give a little bit more background on the problem and present a solution in the form of [our newly released laravel-csp](https://github.com/spatie/laravel-csp) package.

## Introducing CSP #

You might think that yeah, I'm not going to run rogue JavaScript. And while you might use well known JS packages, are you really sure their dependencies (and their dependencies, etc...) don't contain unwanted code? It's actually very easy for someone to hide malicious behaviour. To really feel the problem you should read [this excellent blog post](https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5) by [David Gilbertson](https://twitter.com/D__Gilbertson). Spoiler: it's nearly impossible for you to detect rogue JavaScript (unless you manually read all the JavaScript code on your site).

Luckily there's a good solution to this problem. Every browser has support for secure content policies. In short it works as follows: you set an http header named `Content-Security-Policy`. Its value contains a description of all sources where content may load from. So it's not constricted to JavaScript, you can also determine of which sources images, styles, etc... can be loaded from. You can even specify to which hosts the forms on your site are allowed to post to. For more info on CSP itself and which directives you can use in it, head over to [Mozilla's excellent documentation on CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

## Implementing CSP in a Laravel app #

To easily add a Content Security Policy to a Laravel app, our team at [Spatie](https://spatie.be) has created [a new package called laravel-csp](https://github.com/spatie/laravel-csp). Once installed it allows you to create a policy class. It can look like this:

```
namespace App\Services\Csp\Policies;

use Spatie\Csp\Directive;
use Spatie\Csp\Policies\Policy;

class MyCustomPolicy extends Policy
{
    public function configure()
    {
        parent::configure();
        
        $this->addDirective(Directive::SCRIPT, 'www.google.com');
    }
}
```

This policy will allow you to run everything from your own site (that's being handled in the base policy `Spatie\Csp\Profiles\Policy`) and it allows script to load from the `www.google.com` domain.

On real-life sites policies will probably be a bit bigger, because on most sites a lot of 3th party services are used. Here is the policy used on this very page you're reading:

```
namespace App\Services\Csp;

use Spatie\Csp\Directive;
use Spatie\Csp\Policies\Policy as BasePolicy;

class Policy extends BasePolicy
{
    public function configure()
    {
        $this
            ->addGeneralDirectives()
            ->addDirectivesForBootstrap()
            ->addDirectivesForCarbon()
            ->addDirectivesForGoogleFonts()
            ->addDirectivesForGoogleAnalytics()
            ->addDirectivesForGoogleTagManager()
            ->addDirectivesForTwitter()
            ->addDirectivesForYouTube();
    }

    protected function addGeneralDirectives(): self
    {
        return $this
            ->addDirective(Directive::BASE, 'self')
            ->addNonceForDirective(Directive::SCRIPT)
            ->addDirective(Directive::SCRIPT, [
                'murze.be',
                'murze.be.test',
            ])
            ->addDirective(Directive::STYLE, [
                'murze.be',
                'murze.be.test',
                'unsafe-inline',
            ])
            ->addDirective(Directive::FORM_ACTION, [
                'murze.be',
                'murze.be.test',
                'sendy.murze.be',
            ])
            ->addDirective(Directive::IMG, [
                '*',
                'unsafe-inline',
                'data:',
            ])
            ->addDirective(Directive::OBJECT, 'none');
    }

    protected function addDirectivesForBootstrap(): self
    {
        return $this
            ->addDirective(Directive::FONT, ['*.bootstrapcdn.com'])
            ->addDirective(Directive::SCRIPT, ['*.bootstrapcdn.com'])
            ->addDirective(Directive::STYLE, ['*.bootstrapcdn.com']);
    }

    protected function addDirectivesForCarbon(): self
    {
        return $this->addDirective(Directive::SCRIPT, [
            'srv.carbonads.net',
            'script.carbonads.com',
            'cdn.carbonads.com',
        ]);
    }

    protected function addDirectivesForGoogleFonts(): self
    {
        return $this
            ->addDirective(Directive::FONT, 'fonts.gstatic.com')
            ->addDirective(Directive::SCRIPT, 'fonts.googleapis.com')
            ->addDirective(Directive::STYLE, 'fonts.googleapis.com');
    }

    protected function addDirectivesForGoogleAnalytics(): self
    {
        return $this->addDirective(Directive::SCRIPT, '*.google-analytics.com');
    }

    protected function addDirectivesForGoogleTagManager(): self
    {
        return $this->addDirective(Directive::SCRIPT, '*.googletagmanager.com');
    }

    protected function addDirectivesForTwitter(): self
    {
        return $this
            ->addDirective(Directive::SCRIPT, [
                'platform.twitter.com',
                '*.twimg.com',
            ])
            ->addDirective(Directive::STYLE, [
                'platform.twitter.com',
            ])
            ->addDirective(Directive::FRAME, [
                'platform.twitter.com',
                'syndication.twitter.com',
            ])
            ->addDirective(Directive::FORM_ACTION, [
                'platform.twitter.com',
                'syndication.twitter.com',
            ]);
    }

    protected function addDirectivesForYouTube(): self
    {
        return $this->addDirective(Directive::FRAME, '*.youtube.com');
    }
}
```

Go ahead an inspect the `Content-Security-Policy` header of this page to see the result of the policy above.

## In closing #

The package has some more features, including support for [nonces](https://github.com/spatie/laravel-csp#using-inline-scripts-and-styles), and [reporting](https://github.com/spatie/laravel-csp#reporting-csp-errors). To learn more head over to the readme of the package [on GitHub](https://github.com/spatie/laravel-csp). Be sure to also read the aforementioned [Mozilla docs on CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

To test out how solid the headers of your site are security-wise check out [https://securityheaders.io/](https://securityheaders.io/) where you can run an a test. This blog gets an A+ score, which I'm proud of.

![a plus score](https://freek.dev/uploads/media/csp/aplus.png)

[Laravel-csp](https://github.com/spatie/laravel-csp) is not the first package [my team](https://spatie.be) has made. On our company website you'll find lists of each [Laravel](https://spatie.be/en/opensource/laravel), [PHP](https://spatie.be/en/opensource/php) and [JavaScript](https://spatie.be/en/opensource/javascript) packages we've made before. I'm pretty sure you'll find something useful for your next project.