[php] Laravel 5 - redirect to HTTPS

Working on my first Laravel 5 project and not sure where or how to place logic to force HTTPS on my app. The clincher here is that there are many domains pointing to the app and only two out of three use SSL (the third is a fallback domain, long story). So I'd like to handle this in my app's logic rather than .htaccess.

In Laravel 4.2 I accomplished the redirect with this code, located in filters.php:

App::before(function($request)
{
    if( ! Request::secure())
    {
        return Redirect::secure(Request::path());
    }
});

I'm thinking Middleware is where something like this should be implemented but I cannot quite figure this out using it.

Thanks!

UPDATE

If you are using Cloudflare like I am, this is accomplished by adding a new Page Rule in your control panel.

This question is related to php laravel laravel-routing laravel-5

The answer is


You can use RewriteRule to force ssl in .htaccess same folder with your index.php
Please add as picture attach, add it before all rule others setting ssl .htaccess


Alternatively, If you are using Apache then you can use .htaccess file to enforce your URLs to use https prefix. On Laravel 5.4, I added the following lines to my .htaccess file and it worked for me.

RewriteEngine On

RewriteCond %{HTTPS} !on
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

For Laravel 5.6, I had to change condition a little to make it work.

from:

if (!$request->secure() && env('APP_ENV') === 'prod') {
return redirect()->secure($request->getRequestUri());
}

To:

if (empty($_SERVER['HTTPS']) && env('APP_ENV') === 'prod') {
return redirect()->secure($request->getRequestUri());
}

This worked out for me. I made a custom php code to force redirect it to https. Just include this code on the header.php

<?php
if (isset($_SERVER['HTTPS']) &&
    ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) ||
    isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
    $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $protocol = 'https://';
}
else {
  $protocol = 'http://';
}
$notssl = 'http://';
if($protocol==$notssl){
    $url = "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";?>
    <script> 
    window.location.href ='<?php echo $url?>';
    </script> 
 <?php } ?>

The answers above didn't work for me, but it appears that Deniz Turan rewrote the .htaccess in a way that works with Heroku's load balancer here: https://www.jcore.com/2017/01/29/force-https-on-heroku-using-htaccess/

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

A little different approach, tested in Laravel 5.7

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Str;

class ForceHttps
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {    
        if ( !$request->secure() && Str::startsWith(config('app.url'), 'https://') ) {
            return redirect()->secure($request->getRequestUri());
        }
        return $next($request);
    }
}

PS. Code updated based on @matthias-lill's comments.


I'm adding this alternative as I suffered a lot with this issue. I tried all different ways and nothing worked. So, I came up with a workaround for it. It might not be the best solution but it does work -

FYI, I am using Laravel 5.6

if (App::environment('production')) {
    URL::forceScheme('https');
}

production <- It should be replaced with the APP_ENV value in your .env file


I am using in Laravel 5.6.28 next middleware:

namespace App\Http\Middleware;

use App\Models\Unit;
use Closure;
use Illuminate\Http\Request;

class HttpsProtocol
{
    public function handle($request, Closure $next)
    {
        $request->setTrustedProxies([$request->getClientIp()], Request::HEADER_X_FORWARDED_ALL);

        if (!$request->secure() && env('APP_ENV') === 'prod') {
            return redirect()->secure($request->getRequestUri());
        }

        return $next($request);
    }
}

This work for me in Laravel 7.x in 3 simple steps using a middleware:

1) Generate the middleware with command php artisan make:middleware ForceSSL

Middleware

<?php

namespace App\Http\Middleware;

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

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

        return $next($request);
    }
}

2) Register the middleware in routeMiddleware inside Kernel file

Kernel

protected $routeMiddleware = [
    //...
    'ssl' => \App\Http\Middleware\ForceSSL::class,
];

3) Use it in your routes

Routes

Route::middleware('ssl')->group(function() {
    // All your routes here

});

here the full documentation about middlewares

========================

.HTACCESS Method

If you prefer to use an .htaccess file, you can use the following code:

<IfModule mod_rewrite.c>
    RewriteEngine On 
    RewriteCond %{SERVER_PORT} 80 
    RewriteRule ^(.*)$ https://yourdomain.com/$1 [R,L]
</IfModule>

Regards!


Similar to manix's answer but in one place. Middleware to force HTTPS

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

class ForceHttps
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (!app()->environment('local')) {
            // for Proxies
            Request::setTrustedProxies([$request->getClientIp()], 
                Request::HEADER_X_FORWARDED_ALL);

            if (!$request->isSecure()) {
                return redirect()->secure($request->getRequestUri());
            }
        }

        return $next($request);
    }
}

I found out that this worked for me. First copy this code in the .htaccess file.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

in IndexController.php put

public function getIndex(Request $request)
{
    if ($request->server('HTTP_X_FORWARDED_PROTO') == 'http') {

        return redirect('/');
    }

    return view('index');
}

in AppServiceProvider.php put

public function boot()
{
    \URL::forceSchema('https');

}

In AppServiceProvider.php every redirect will be go to url https and for http request we need once redirect so in IndexController.php Just we need do once redirect


Here's how to do it on Heroku

To force SSL on your dynos but not locally, add to end of your .htaccess in public/:

# Force https on heroku...
# Important fact: X-forwarded-Proto will exist at your heroku dyno but wont locally.
# Hence we want: "if x-forwarded exists && if its not https, then rewrite it":
RewriteCond %{HTTP:X-Forwarded-Proto} .
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

You can test this out on your local machine with:

curl -H"X-Forwarded-Proto: http" http://your-local-sitename-here

That sets the header X-forwarded to the form it will take on heroku.

i.e. it simulates how a heroku dyno will see a request.

You'll get this response on your local machine:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://tm3.localhost:8080/">here</a>.</p>
</body></html>

That is a redirect. That is what heroku is going to give back to a client if you set the .htaccess as above. But it doesn't happen on your local machine because X-forwarded won't be set (we faked it with curl above to see what was happening).


The easiest way to redirect to HTTPS with Laravel is by using .htaccess

so all you have to do is add the following lines to your .htaccess file and you are good to go.

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Make sure you add it before the existing(*default) code found in the .htaccess file, else HTTPS will not work. This is because the existing(default) code already handles a redirect which redirects all traffic to the home page where the route then takes over depending on your URL

so putting the code first means that .htaccess will first redirect all traffic to https before the route takes over


If you're using CloudFlare, you can just create a Page Rule to always use HTTPS: Force SSL Cloudflare This will redirect every http:// request to https://

In addition to that, you would also have to add something like this to your \app\Providers\AppServiceProvider.php boot() function:

if (env('APP_ENV') === 'production' || env('APP_ENV') === 'dev') {
     \URL::forceScheme('https');
}

This would ensure that every link / path in your app is using https:// instead of http://.


What about just using .htaccess file to achieve https redirect? This should be placed in project root (not in public folder). Your server needs to be configured to point at project root directory.

<IfModule mod_rewrite.c>
   RewriteEngine On
   # Force SSL
   RewriteCond %{HTTPS} !=on
   RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
   # Remove public folder form URL
   RewriteRule ^(.*)$ public/$1 [L]
</IfModule>

I use this for laravel 5.4 (latest version as of writing this answer) but it should continue to work for feature versions even if laravel change or removes some functionality.


In Laravel 5.1, I used: File: app\Providers\AppServiceProvider.php

public function boot()
{
    if ($this->isSecure()) {
        \URL::forceSchema('https');
    }
}

public function isSecure()
{
    $isSecure = false;
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
        $isSecure = true;
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
        $isSecure = true;
    }

    return $isSecure;
}

NOTE: use forceSchema, NOT forceScheme


This is for Larave 5.2.x and greater. If you want to have an option to serve some content over HTTPS and others over HTTP here is a solution that worked for me. You may wonder, why would someone want to serve only some content over HTTPS? Why not serve everything over HTTPS?

Although, it's totally fine to serve the whole site over HTTPS, severing everything over HTTPS has an additional overhead on your server. Remember encryption doesn't come cheap. The slight overhead also has an impact on your app response time. You could argue that commodity hardware is cheap and the impact is negligible but I digress :) I don't like the idea of serving marketing content big pages with images etc over https. So here it goes. It's similar to what others have suggest above using middleware but it's a full solution that allows you to toggle back and forth between HTTP/HTTPS.

First create a middleware.

php artisan make:middleware ForceSSL

This is what your middleware should look like.

<?php

namespace App\Http\Middleware;

use Closure;

class ForceSSL
{

    public function handle($request, Closure $next)
    {

        if (!$request->secure()) {
            return redirect()->secure($request->getRequestUri());
        }

        return $next($request);
    }
}

Note that I'm not filtering based on environment because I have HTTPS setup for both local dev and production so there is not need to.

Add the following to your routeMiddleware \App\Http\Kernel.php so that you can pick and choose which route group should force SSL.

    protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'forceSSL' => \App\Http\Middleware\ForceSSL::class,
];

Next, I'd like to secure two basic groups login/signup etc and everything else behind Auth middleware.

Route::group(array('middleware' => 'forceSSL'), function() {
/*user auth*/
Route::get('login', 'AuthController@showLogin');
Route::post('login', 'AuthController@doLogin');

// Password reset routes...
Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('password/reset', 'Auth\PasswordController@postReset');

//other routes like signup etc

});


Route::group(['middleware' => ['auth','forceSSL']], function()
 {
Route::get('dashboard', function(){
    return view('app.dashboard');
});
Route::get('logout', 'AuthController@doLogout');

//other routes for your application
});

Confirm that your middlewares are applied to your routes properly from console.

php artisan route:list

Now you have secured all the forms or sensitive areas of your application, the key now is to use your view template to define your secure and public (non https) links.

Based on the example above you would render your secure links as follows -

<a href="{{secure_url('/login')}}">Login</a>
<a href="{{secure_url('/signup')}}">SignUp</a>

Non secure links can be rendered as

<a href="{{url('/aboutus',[],false)}}">About US</a></li>
<a href="{{url('/promotion',[],false)}}">Get the deal now!</a></li>

What this does is renders a fully qualified URL such as https://yourhost/login and http://yourhost/aboutus

If you were not render fully qualified URL with http and use a relative link url('/aboutus') then https would persists after a user visits a secure site.

Hope this helps!


for laravel 5.4 use this format to get https redirect instead of .htaccess

namespace App\Providers;

use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        URL::forceScheme('https');
    }
}

An other option that worked for me, in AppServiceProvider place this code in the boot method:

\URL::forceScheme('https');

The function written before forceSchema('https') was wrong, its forceScheme


The easiest way would be at the application level. In the file

app/Providers/AppServiceProvider.php

add the following:

use Illuminate\Support\Facades\URL;

and in the boot() method add the following:

$this->app['request']->server->set('HTTPS', true);
URL::forceScheme('https');

This should redirect all request to https at the application level.

( Note: this has been tested with laravel 5.5 LTS )


You can simple go to app -> Providers -> AppServiceProvider.php

add two lines

use Illuminate\Support\Facades\URL;

URL::forceScheme('https');

as shows in the following codes:

use Illuminate\Support\Facades\URL;

class AppServiceProvider extends ServiceProvider
{
   public function boot()
    {
        URL::forceScheme('https');

       // any other codes here, does not matter.
    }

Examples related to php

I am receiving warning in Facebook Application using PHP SDK Pass PDO prepared statement to variables Parse error: syntax error, unexpected [ Preg_match backtrack error Removing "http://" from a string How do I hide the PHP explode delimiter from submitted form results? Problems with installation of Google App Engine SDK for php in OS X Laravel 4 with Sentry 2 add user to a group on Registration php & mysql query not echoing in html with tags? How do I show a message in the foreach loop?

Examples related to laravel

Parameter binding on left joins with array in Laravel Query Builder Laravel 4 with Sentry 2 add user to a group on Registration Target class controller does not exist - Laravel 8 Visual Studio Code PHP Intelephense Keep Showing Not Necessary Error The POST method is not supported for this route. Supported methods: GET, HEAD. Laravel How to fix 'Unchecked runtime.lastError: The message port closed before a response was received' chrome issue? Post request in Laravel - Error - 419 Sorry, your session/ 419 your page has expired Expected response code 250 but got code "530", with message "530 5.7.1 Authentication required How can I run specific migration in laravel Laravel 5 show ErrorException file_put_contents failed to open stream: No such file or directory

Examples related to laravel-routing

Laravel 5.2 Missing required parameters for [Route: user.profile] [URI: user/{nickname}/profile] Laravel: How to Get Current Route Name? (v5 ... v7) Set session variable in laravel Laravel 5 - redirect to HTTPS How Can I Remove “public/index.php” in the URL Generated Laravel? Download files in laravel using Response::download Laravel Controller Subfolder routing My Routes are Returning a 404, How can I Fix Them?

Examples related to laravel-5

Expected response code 250 but got code "530", with message "530 5.7.1 Authentication required Laravel 5 show ErrorException file_put_contents failed to open stream: No such file or directory Can't install laravel installer via composer Including a css file in a blade template? No Application Encryption Key Has Been Specified How to Install Font Awesome in Laravel Mix Laravel Migration Error: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes Laravel 5.4 redirection to custom url after login How to set the default value of an attribute on a Laravel model laravel 5.3 new Auth::routes()