Enabling security headers for your website with PHP & Laravel

2018-03-07 | Tutorials

When I was working on this blog, I wanted to get on the whole security headers train that is currently passing through the Internet. In this blog post, I will teach you about the headers, the ways to modify them with PHP and I will also tell you how to implement them in your Laravel application properly.

Let me start by introducing the SecurityHeaders.com - an awesomely simple web application that quickly gives you the security overview of HTTP headers your website sends out when visited. On top of that, it also grades the level of security these headers provide, on the A+ to F scale. The application was developed by @Scott Helme, who regularly writes about it on his blog, so be sure to follow him for updates.

When I stumbled upon the application, I was almost immediately convinced that I needed to get that A+ - after all, the security overview on the page provided me with most of the information I needed. In 4 simple steps I was able to pull my grade up by 5 marks:

Scanning itself was pretty straightforward, I accessed securityheaders.com, input my website, cowardly hid my page from latest scans and then scrolled down to read the overview.

danieldusek.com in securityheaders.io scanner

Security HTTP headers

Next thing I needed to do was to catch up on all the security headers reading I should have done years ago. There are 6 important security headers, where at least the good half of them should have a constant value, which could generally be used:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Purpose of these headers in order of mention - to prevent browsers from trying to guess content type based on file contents; to forbid your page from being loaded in an (i)frame and to enable built-in XSS protection in the browser. Note that the last header does not supplement proper user input sanitization. I recommend these as default values in 99% of use cases.

I also needed to add Referrer-Policy header that specifies how much information will be provided to sites I link to from my web. Since I do not store any sensitive information on any of my URLs, I went for no-referrer-when-downgrade which passes the referrer information to any HTTPS target page, but does not pass anything for HTTP targets. If I did store sensitive information in any of my URLs, I would probably choose no-referrer.

Then there are two essential headers that may get misconfigured and have a negative impact on your webpage - Content-Security-Policy and Strict-Transport-Security. A lot has been written on the topic, so please do the reading before setting values for them.

Setting headers using PHP

PHP in version 5.2 and lower has only header() function, the more up to date versions have also function header_remove(). The former case makes it possible to add & update headers just by using header() function, but does not allow removal of the headers. There is a workaround though - it is possible to overwrite unwanted header value with dummy or blank value.

Newer versions of PHP have dedicated header_remove() function that finally allows the deletion of headers. A snippet of code explaining the usage of these functions is coming right up:

<?php
// 1 - Add some custom headers to response.
header('X-My-Powered-By: Laravel');
header('Access-Control-Allow-Origin: *'); // bad idea
header('X-XSS-Protection: 1; mode=block'); // good idea

// 2 - Modify some headers that already exist
header('Access-Control-Allow-Origin: https://danieldusek.com');
header('Content-Type: application/json');

// 3 - Special usage of header() function to fake-remove header
header('X-My-Powered-By:');

// 4 - Truly remove X-My-Powered-By header
header_remove('X-My-Powered-By');

// 5 - Remove all the headers
header_remove();
?>

For convenience, I created copy-paste ready gist PHP file for mentioned 'constant' headers.

Security headers in Laravel application

I was considering including the header setting logic into index.php through which all requests are routed when it hit me - there must be a way to hook onto the request that is being processed and add some more headers. And of course, there is, and it is called middleware.

Middleware is basically a code that executes on every request that is processed by Laravel application. All I needed to do then was to create a middleware of my own that would append (and remove) some headers.

<?php
namespace App\Http\Middleware;
use Closure;

class SecureHeaders
{
    // Enumerate headers which you do not want in your application's responses.
    // Great starting point would be to go check out @Scott_Helme's:
    // https://securityheaders.com/
    private $unwantedHeaderList = [
        'X-Powered-By',
        'Server',
    ];
    public function handle($request, Closure $next)
    {
        $this->removeUnwantedHeaders($this->unwantedHeaderList);
        $response = $next($request);
        $response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('X-Frame-Options', 'DENY');
        $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
        $response->headers->set('Content-Security-Policy', "style-src 'self'"); // Clearly, you will be more elaborate here.
        return $response;
    }
    private function removeUnwantedHeaders($headerList)
    {
        foreach ($headerList as $header)
            header_remove($header);
    }
}
?>

This file is best to put under /app/http/middleware folder in your project. Once the file is there, there is one more step to take - updating /app/http/kernel.php file. We have middleware prepared, and now we need to tell the Laravel we want it to run on every request:

protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,

        // Here we add newly created middleware
        \App\Http\Middleware\SecureHeaders::class 
    ];

After this is done, this is what happens when requests reach your application:

  1. A request is routed via index.php.
  2. All the previously defined middleware is executed.
  3. Secure headers middleware is executed, and headers are appended to the response.
  4. A response is sent back.

And there we go! Security headers are taken care of and easily manageable.

References & Resources

  1. Question Set custom header for any response [available on laracasts.com].
  2. Question Where can I set headers in laravel [available on stackoverflow.com].
  3. Issue Can't remove X-Powered-By header [available in symphony's repository on Github.com].
  4. A new security header: Referrer policy from Scott Helme.