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:
1 |
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:
1 |
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:
1 2 3 |
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:
1 |
php artisan make:middleware HttpRedirect |
This creates the file app/Http/Middleware/HttpRedirect.php
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?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:
1 |
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.
1 2 3 4 5 6 7 8 |
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):
1 |
return redirect()->secure($request->getRequestUri(), 301); |
If you have development, staging and production environments and you want HTTP redirection for both staging and production:
1 2 3 4 5 6 7 8 |
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:
1 2 3 |
protected $middleware = [ ... \App\Http\Middleware\HttpRedirect::class, |
Clear the configuration:
1 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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:
1 2 |
use Illuminate\Support\Facades\App; use URL; |
Add the following code to the boot function:
1 2 3 4 5 6 |
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.
I design software for enterprise-class systems and data centers. My background is 30+ years in storage (SCSI, FC, iSCSI, disk arrays, imaging) virtualization. 20+ years in identity, security, and forensics.
For the past 14+ years, I have been working in the cloud (AWS, Azure, Google, Alibaba, IBM, Oracle) designing hybrid and multi-cloud software solutions. I am an MVP/GDE with several.
April 1, 2022 at 2:01 PM
i have this error “syntax error, unexpected ‘return’ (T_RETURN)”.
th return in the if statment is not accepted, any help please.
thanx
April 1, 2022 at 2:04 PM
With the details you provided, I have no idea. Please create a question on Stack Overflow and send me the link. I will try to help solve your problem.