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:
Once I finished I have an 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'];
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');
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');
informs browsers that the site should only be accessed using HTTPS
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
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 ");
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');
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=()');
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);
}
}
}
Subscribe to my newsletter for the latest updates on my books and digital products.
Find posts, tutorials, and resources quickly.
Subscribe to my newsletter for the latest updates on my books and digital products.