Introduction

Once you have an SSL certificate configured, the next step is to redirect unencrypted traffic. There are several methods of doing this. Within your application (Laravel), by the web server (Apache or Nginx) or by the frontend (load balancer). This article will redirect HTTP requests to HTTPS in Laravel using middleware.

If you are also deploying a frontend load balancer, configure both HTTP and HTTPS frontends. In most cases, you will forward traffic from the load balancer to the backend (Laravel) via HTTP and not by HTTPS. This is called SSL Offloading. This means your Laravel middleware must detect the protocol (HTTP or HTTPS) that the client connected to the load balancer and ignore the protocol that the load balancer is using to connect to the backend. Otherwise, the middleware will detect HTTP even if the client connected to the load balancer using HTTPS, and the client will go into a redirect loop.

In this article, I will use yourdomain.com. Replace with your domain name.

Laravel middleware only supports files served by routes. Files that are not served by Laravel, such as /js/app.js will NOT be redirected. This is one of the reasons I like to have HTTP Redirection as several layers (load balancer, web server, application framework). Another reason is to ensure that more than one service layer enforces HTTP Redirection.

Configure .env

This article supports two environments, development and production. The development settings will not redirect HTTP to HTTPS. The production environment will redirect. The environment will be detected by the APP_ENV setting.

Production Configuration:

  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://yourdomain.com

Development Configuration:

  • APP_ENV=local
  • APP_DEBUG=true
  • APP_URL=http://localhost:8000

The application environment labels local and production are used to enable/disable certain features in Laravel.

Initial Testing

Open a web browser and connect to your site via HTTP: http://yourdomain.com. Verify that your site loads correctly, and you are not redirected to HTTPS. Note: some TLD domains such as .dev automatically redirect in browsers. If this is the case for you, use the curl command method below.

Open a command prompt and run this command:

curl -I http://yourdomain.com

We are interested in the first part of the output which is the HTTP status code. If HTTP redirection is disabled, you should receive a 200 response:

HTTP/1.1 200 OK

For this article, we want a 200 response so that we can implement and test HTTP redirection.

If HTTP redirection is enabled, then you will receive a 3xx response with an HTTP Location header:

HTTP/1.1 302 Found
.... 
Location: https://yourdomain.com

Before continuing, disable redirects in your web server or frontend (load balancer). Save your changes, so that you can reenable redirection at the frontend or at the webserver.

Note: browsers tend to cache HTTP redirects. You might need to disable the browser cache.

Disable Chrome Cache

  • Open the Chrome Developer Tools (F12).
  • Go to the Network tab and make sure Disable cache is ticked.
  • Keep the Developer Tools open while testing.

Create the Middleware

Using artisan create the middleware template:

php artisan make:middleware HttpRedirect

This creates the file app/Http/Middleware/HttpRedirect.php.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class HttpRedirect
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        return $next($request);
    }
}

Near the top of the file add:

use Illuminate\Support\Facades\App;

Modify the function handle(). Note the following features:

  • Check if the request is using HTTP: !$request->secure()
  • Check if the environment is production: App::environment('production')
  • If both requirements are met, redirect the client to the same URI using HTTPS. Otherwise, proceed to the next handler.
    public function handle(Request $request, Closure $next)
    {
        if (!$request->secure() && App::environment('production') {
                return redirect()->secure($request->getRequestUri());
        }

        return $next($request);
    }

The above redirect will return the HTTP code 302. For permanent HTTP to HTTPS redirects, return HTTP code 301 (permanent redirect):

 return redirect()->secure($request->getRequestUri(), 301);

If you have development, staging and production environments and you want HTTP redirection for both staging and production:

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

        return $next($request);
    }

Edit App/Http/Kernel.php and add the middleware to $middleware:

    protected $middleware = [
        ...
        \App\Http\Middleware\HttpRedirect::class,

Clear the configuration:

php artisan optimize:clear

Supporting Proxy Frontends

If you are using a load balancer that connects to your Laravel backend using HTTP, detect the HTTP header X-Forwarded-Proto. Use this code for the handle() function instead:

    public function handle(Request $request, Closure $next)
    {
        // If the client connected to the frontend (load balancer) via https
        // redirection is not necessary

        if ($request->headers->has('X-Forwarded-Proto')) {
            if (strcmp($request->header('X-Forwarded-Proto'), 'https') === 0) {
                return $next($request);
            }
        }

        if (!$request->secure() && App::environment(['staging', 'production'])) {
                return redirect()->secure($request->getRequestUri(), 301);
        }

        return $next($request);
    }

Warning

If your Laravel application does not have a proxy (load balancer) accepting traffic, do not add the proxy code. A smart hacker could manually add the header X-Forwarded-Proto and bypass the HTTP Redirect feature.

If you allow your Laravel backend to be accessed from a load balancer and directly from the Internet, add logic to only process the X-Forwarded-Proto header if the request arrives from a known frontend. Google Cloud HTTP(S) Load Balancers use the 130.211.0.0/22 and 35.191.0.0/16 IP address ranges.

Additional Options

The above middleware will redirect requests that are handled by Laravel routes. I also recommend that Laravel always generate content using HTTPS based URLs. Examples are JavaScript and CSS references.

Edit app/Providers/AppServiceProvider.php

Near the top add:

use Illuminate\Support\Facades\App;
use URL;

Add the following code to the boot function:

    public function boot()
    {
        if (App::environment(['staging', 'production'])) {
            URL::forceScheme('https');
        }
    }

Summary

I prefer to implement multiple layers of security. When implementing HTTP Redirection, I try to implement this feature at each service layer. Starting with the backend (Laravel), then with the web server (Apache or Nginx), and finally at the load balancer. Sometimes mistakes are made, and one layer might disable HTTP Redirection. By enabling this feature in more than one service, I have a higher confidence level that clients’ data is and remains encrypted.

Photography Credits

I write free articles about technology. Recently, I learned about Pexels.com which provides free images. The image in this article is courtesy of Pixabay at Pexels.