Laravel HTTP Strict Transport
Security Guide: What It Is
and How to Enable It

stackhawk

StackHawk|October 4, 2021

This article covers configuring Laravel http strict transport security. We'll discuss good reasons for HSTS and the prerequisites first.

Once upon a time, most websites were unencrypted. Website operators limited HTTPS connections to high-value login and payment forms. Today, transport layer security (TLS/SSL) is the default mode for most websites. The internet is a safer place now. As the next evolutionary step, the HTTP strict transport security (HSTS) standard ensures that HTTPS isn't just possible but that unencrypted HTTP connections become impossible. This article covers configuring HSTS in Laravel applications. We'll discuss good reasons for HSTS and the prerequisites first. Then, we'll look at the necessary steps and two options for enabling it.

What Is HSTS?

HTTP strict transport security is a method for websites to announce that they only accept encrypted connections and will continue to do so.

Browsers that understand this announcement will force encrypted connections (https://) to the site even when a user tries to access the site without encryption (http://). That announcement comes in the form of the "Strict-Transport-Security" header.

Since the header is visible only on the first request, you reach the best security only afterward. As an additional mechanism, modern browsers come with a preloaded list of HSTS-enabled domains to enforce security from the first request.

Why Is HSTS Necessary?

Imagine you're browsing the web through an untrusted connection, such as a public Wi-Fi access point. A hacker may have modified the router to run a man-in-the-middle attack. This attack replaces a website with a spoofed version that could grab your login session or other personal data. For example, they might insert a script that sends confidential information to a server they control. A man-in-the-middle who can rewrite HTTP responses can do this even when the website implements protection from cross-site-scripting (XSS). Since the hacker probably won't have a valid certificate for the website, they need to downgrade your connection from HTTPS to HTTP to intercept your interactions. HSTS prevents that.

Of course, this only works if you previously visited the site from a trusted network or if it's on the preloaded list, so your browser knows that it's HTTPS-only.

Should You Use HSTS?

There are a few unusual scenarios where your domain has to accept unencrypted connections either now or in the future. Those may not be for your main website, but subdomains for internal purposes like a sandbox or Intranet website, or if you have a legacy API for IoT devices that don't speak HTTPS. In all other cases, you should enable HSTS for additional security. Handling a lot of valuable personal data like payment information strengthens the case for HSTS even more.

What Are the Prerequisites For HSTS?

The obvious prerequisite is that your website supports HTTPS connections already. It means that you've created a key pair and acquired a certificate from a trustworthy certificate authority. Let's Encrypt offers such certificates for free. You also need to configure your webserver (nginx, Apache, IIS, etc.) to enable TLS. I'm assuming that you've already done this, so I won't cover the TLS setup in this article. If you aren't there yet, you can find many guides, such as this official article from nginx on configuring Let's Encrypt with their webserver.

If your website uses the root domain and multiple subdomains (e.g., "example.com", "www.example.com", "somethingelse.example.com"), you need a certificate for each or a wildcard certificate covering all subdomains.

After confirming that your website works with HTTPS, you also need to consider how you'll handle unencrypted HTTP connections from now on. One option would be to reject them, but that stops users from finding your website if they enter it with http:// or without any prefix and their browser assumes http:// instead of https://. The better option is to ensure that every insecure request redirects to a secure request.

How Can I Implement HTTP to HTTPS Redirects?

One way to do this is to configure your webserver to issue a redirect. If that's your preferred way, check the documentation for your webserver and edit the configuration files accordingly.

You can also do this in Laravel with custom middleware. Create the new file app/Http/Middleware/HttpRedirect.php with the following content:

Laravel
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;

class HttpRedirect
{

    public function handle(Request $request, Closure $next)
    {
        if (!$request->secure() && App::environment('production')) {
                return redirect()->secure($request->getRequestUri(), 301);
        }

        return $next($request);
    }

}

Note that the redirect uses the HTTP 301 status code. It indicates that this is a permanent redirect, which is in line with HSTS. Remember, we want no exceptions from HTTPS-only. To ensure that the middleware runs on every request, add it to the $middleware array in app/Http/Kernel.php:

Laravel
protected $middleware = [
        // other existing middleware
        \App\Http\Middleware\HttpRedirect::class
];

I took the example from this article, which you can refer to if you need further information on creating a custom Laravel middleware.

How Can I Configure HSTS?

After meeting the prerequisites, you can finally add "Strict-Transport-Security" to your responses. As mentioned in the beginning, I will show you two ways of doing that. First, however, let's discuss the configuration options:

With max-age, you can specify how long browsers should remember that the site is HTTPS-only. Since there's no going back to unencrypted HTTP during this time, you should test the setup with a few minutes at first. Later, you can increase this time. Most websites set this to one or two years.

With includeSubdomains, you can specify that the directive is valid for subdomains as well. That means that if you set the header for "example.com", the browser will consider requests to "www.example.com" (or "somethingelse.example.com") HTTPS-only as well.

With preload, you can specify that browsers may include your domain in their preloaded list of HTTPS-only host names.

Adding the HSTS Header With Custom Middleware

You can create custom middleware to add the "Strict-Transport-Security" header to every request, similar to the HTTP-to-HTTPS redirect middleware we saw earlier. If you want to take this route, create the new file app/Http/Middleware/HSTS.php with the following content:

Laravel
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class HSTS
{

    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        $response->header('Strict-Transport-Security', 'max-age=31536000; includeSubdomains');

        return $response;
    }

}

In the example, we set the max-age to 31,536,000 seconds, which is equivalent to one year. As I mentioned before, you should probably start setting this to 300 seconds (or 5 minutes) for testing purposes first. We also added the includeSubdomains option. Once again, to ensure that the middleware runs on every request, add it to the $middleware array in app/Http/Kernel.php:

Laravel
protected $middleware = [
        // other existing middleware
        \App\Http\Middleware\HSTS::class
];

Using the Secure Headers Library

There's a third-party library that supports HSTS and several other security-related HTTP headers that could be interesting or relevant for your Laravel website. It can be an alternative option to custom middleware, especially when you're interested in these other security headers as well.

To install the library, navigate to your Laravel project directory and enter the following two commands in your console:

Laravel
composer require bepsvpt/secure-headers
php artisan vendor:publish --provider="Bepsvpt\SecureHeaders\SecureHeadersServiceProvider"

Next, add it to the $middleware array in app/Http/Kernel.php like this:

Laravel
protected $middleware = [
        // other existing middleware
        \Bepsvpt\SecureHeaders\SecureHeadersMiddleware::class,
];

After this setup, you can enable HSTS by editing the 'hsts' configuration block in config/secure-headers.php. Set 'enable' to true and the other parameters (max-age, include-sub-domains, and preload) to the desired settings. You're done and have enabled HSTS for your website!

Other Security Headers

If you scroll down config/secure-headers.php, you can see a 'csp' configuration block. If you configured a Content Security Policy (CSP) through another library as shown in a previous article on this blog, make sure to set 'enable' to false here to avoid duplicate configuration. Otherwise, this is an excellent opportunity to enable CSP and HSTS with a single library.

The library comes with a few additional preconfigured headers. For example, X-Content-Type-Options prevents browsers from executing scripts or styles if they don't have the correct Content-Type.

X-Frame-Options prevents other websites from loading yours in an iframe. Unless your Laravel website explicitly offers embeddable content, this is a good policy.

Permission policies describe the browser features that the website wants to use. Disabling features helps with XSS attacks, where an attacker uses browser features that the website doesn't need, similar to content security policies.

I won't discuss all options here, but the comments in the file include helpful links to read about other security headers.

Ready to Test Your App

What's Next?

Once you've configured HSTS with the preload option, you can request the inclusion of your website in the preload lists. Go to the HSTS Preload website to learn more about the prerequisites for it and submit your domain. In any case, by configuring HSTS, you've already improved your website security.

We've mentioned XSS attacks in this article, but make sure to check out our articles about cross-site request forgery (CSRF) and path traversal attacks, as these are other security issues you should prevent in your Laravel website.

This post was written by Lukas Rosenstock. Lukas is an independent PHP and web developer turned API consultant and technical writer. He works with clients of all sizes to design, implement, and document great APIs. His focus is on creating integrations and mashups that highlight the values of APIs, and educate developers about their potential.


StackHawk  |  October 4, 2021