Laravel Security Headers

David Carr

Development Laravel Framework

This weekend, I changed the design of this blog whilst doing so I wanted to add the security headers for content security policies, these tell the application what it can and cannot run, There's a great website called https://securityheaders.com which will scan a URL and tell you what your level is.

If you have no headers set up you'll get an F grade, which is bad! 

Before I started my rating:

an F grade website

Once I finished I have an A rating:

A rating

For detailed information about security headers read Daniel DuĊĦek blog that explains this really well https://danieldusek.com/enabling-security-headers-for-your-website-with-php-and-laravel.html 

Still here? great I'll go over what I've done.

I created a middleware class called SecurityHeaders.php inside App\Http\Middleware of my Laravel application

Add this middleware to the Middleware group inside App\Http\Kernal.php 

protected $middleware = [
    // \App\Http\Middleware\TrustHosts::class,
    \App\Http\Middleware\TrustProxies::class,
    \Fruitcake\Cors\HandleCors::class,
    \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\SecurityHeaders::class,
];

Set the headers to be turned off, this provide would be attackers information about the server, you don't need to advertise these to better to turn them off.

private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];

Referrer Policy

Sets how much information should be sent with requests, in my case I chose the option to not send the referer header for requests to less secure destinations.

$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');

XSS Protection

Stops loading of pages when they detect reflected cross-site scripting (XSS) attacks

I set: Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.

$response->headers->set('X-XSS-Protection', '1; mode=block');

Strict Transport Security

informs browsers that the site should only be accessed using HTTPS

$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

Content Security Policy

The content security policy sets weather a browser can run JS / CSS on page load.

You will want to either det a URL specifically or a wildcard like *.domain to allow the subdomain of the given domain to run

$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self'; media-src 'self'; frame-src 'self' platform.twitter.com github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self'; report-uri ");

Expect-CT

The Expect-CT header lets sites opt in to reporting and/or enforcement of Certificate Transparency requirements

$response->headers->set('Expect-CT', 'enforce, max-age=30');

Permissions-Policy

Sets what permissions the device load the page is given

$response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');

The complete class:

With this class in place upload your changes to your server and re-run https://securityheaders.com

<?php

namespace App\Http\Middleware;

use Closure;

class SecurityHeaders
{
    private $unwantedHeaders = ['X-Powered-By', 'server', 'Server'];

    /**
     * @param $request
     * @param  Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (!app()->environment('testing')) {
            $response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
            $response->headers->set('X-XSS-Protection', '1; mode=block');
            $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
            $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self' platform.twitter.com plausible.io utteranc.es *.cloudflare.com 'unsafe-inline' 'unsafe-eval' plausible.io/js/plausible.js utteranc.es/client.js; style-src 'self' *.cloudflare.com 'unsafe-inline'; img-src 'self' * data:; font-src 'self' data: ; connect-src 'self' plausible.io/api/event; media-src 'self'; frame-src 'self' platform.twitter.com plausible.io utteranc.es github.com *.youtube.com *.vimeo.com; object-src 'none'; base-uri 'self';");
            $response->headers->set('Expect-CT', 'enforce, max-age=30');
            $response->headers->set('Permissions-Policy', 'autoplay=(self), camera=(), encrypted-media=(self), fullscreen=(), geolocation=(self), gyroscope=(self), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=(self), usb=()');
            $response->headers->set('Access-Control-Allow-Origin', '*');
            $response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
            $response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With,X-CSRF-Token');

            $this->removeUnwantedHeaders($this->unwantedHeaders);
        }

        return $response;
    }

    /**
     * @param $headers
     */
    private function removeUnwantedHeaders($headers): void
    {
        foreach ($headers as $header) {
            header_remove($header);
        }
    }
}

 

Copyright © 2006 - 2024 DC Blog - All rights reserved.