60

Our entire site is to be served over https. I have 'https' in each route. However, how do I redirect them to https if they attempt it over http?

Route::group(array('https'), function()
{
     // all of our routes
}
Sajan Parikh
  • 4,668
  • 3
  • 25
  • 28
julie-coderiver
  • 1,119
  • 4
  • 14
  • 16
  • 2
    As many have stated this can be (and IMO should be) done by the web server (NGINX, Apache, etc). – DutGRIFF Mar 09 '15 at 03:25
  • possible duplicate of [Laravel 5 - redirect to HTTPS](http://stackoverflow.com/questions/28402726/laravel-5-redirect-to-https) – insign Aug 28 '15 at 00:48

15 Answers15

97

Using App::before

You might be able to take advantage of the App::before() block in the app/filters.php file.

Change the block to include a simple check to see if the current request is secure, and if not, redirect it.

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

Using Filters

Another option might be to create a filter like so. People generally store this also in app/filters.php.

Route::filter('force.ssl', function()
{
    if( ! Request::secure())
    {
        return Redirect::secure(Request::path());
    }

});

You can then enforce that new filter to any of your routes, route groups, or controllers like this.

Individual Route

Route::get('something', ['before' => 'force.ssl'], function()
{
    return "This will be forced SSL";
});

Route Group

Route::group(['before' => 'force.ssl'], function()
{
    // Routes here.
});

Controller

You'll need to do this in your controller's __construct() method.

public function __construct()
{
    $this->beforeFilter('force.ssl');
}
Sw0ut
  • 741
  • 2
  • 9
  • 29
Sajan Parikh
  • 4,668
  • 3
  • 25
  • 28
  • This is exactly what I was looking for! I knew there was a way to do it cleanly within Laravel filters. Cheers – julie-coderiver Nov 14 '13 at 18:41
  • 4
    Use Request::path(), not Request::getRequestUri() – Andrew U Mar 16 '14 at 21:12
  • 2
    I was using this method, but it suddenly quit working today after I ran an `apt-get upgrade`. Now I'm getting a routing error: "Call to a member function getAction() on a non-object in vendor/laravel/framework/src/Illuminate/Routing/Router.php on line 1528". If I comment out the redirect, it works fine. – Ben Harold Apr 09 '14 at 19:47
  • Hats off to a great and comprehensive answer. – Ibrahim AshShohail Sep 01 '14 at 06:03
  • 1
    This should not be done in Laravel but should be handled in the .htaccess. There's no need for the additional overhead of running Laravel to do this. – Ben Nov 05 '14 at 16:35
  • Is there an extra square bracket in the code for the route group? – superUntitled Dec 04 '14 at 15:00
  • Do this at the server level not in your application – S.. Dec 02 '15 at 21:01
  • 5
    For **Laravel 5 and above** check this solution http://stackoverflow.com/a/28403907/1340784 – Shankar Prakash G Feb 04 '16 at 04:53
  • The issue I had with this, is that it lost any querystrings (like the GA tracking tags). Here is what I ended up doing: http://stackoverflow.com/questions/19967788/laravel-redirect-all-requests-to-https/37128740#37128740 – Jones03 May 10 '16 at 03:27
34

Another answer might be to let your web server handle this. If you are using Apache, you can use the RedirectSSL feature to make sure all requests are going to the HTTPS version of your site, and if not redirect them. This will happen before Laravel even get's the request.

Apache RedirectSSL

If you're on NGINX, you can accomplish this by having two server blocks. One for normal HTTPS on port 80, and another for HTTPS on port 443. Then configure the normal server block to always redirect to ssl version.

server {
    listen 80;
    server_name mydomain.com;
    rewrite ^ https://$server_name$request_uri? permanent;
}

server {
    listen 443;
    server_name mydomain.com;
    ssl on;
    # other server config stuff here.
}

I'd personally go with this option as PHP itself doesn't have to process anything. It's generally cheaper to process a check like this at the web server level.

Sajan Parikh
  • 4,668
  • 3
  • 25
  • 28
  • 3
    Just to clarify, I consider this answer to be the preferred way of doing things rather than the accepted answer. – Sajan Parikh Mar 31 '14 at 00:31
  • Yes, doing it in the application is more time-consuming, because (doing so) the framework is already initialized for each http request, but doing in the server conf is not. I'd use nginx as proxy and X_FORWARDED_PROTO header – Luis Masuelli Aug 26 '14 at 22:12
  • 3
    My philosophy: trust the web server redirects, but verify in the application. This way you get the best of both worlds. When the web server does it for you, the app experiences no performance hit. When the web server doesn't (eg, misconfiguration), the app acts as a safety net and the performance hit is *well worth* the tradeoff. – bishop Aug 27 '14 at 13:25
11

For users using Laravel 4/5 and Elastic Beanstalk, forcing HTTPS is difficult using these methods because the isSecure() will return false. Further, using .htaccess redirects will result in a redirect loop for Chrome and delayed page load times in Firefox.

This set up is for

  • Laravel 5 and may work for Laravel 3 / 4
  • Application loaded onto Elastic Beanstalk running EC2 server instances
  • Route 53 used for DNS resolution
  • Cloudfront used for global CDN of all assets and enforcing HTTPS
  • I run aws on a Windows machine. Linux may vary slightly?

After hours of my own attempts, I managed to get all HTTP requests forwarded to HTTPS using the following steps:

  1. Obtain an SSL certificate. Guides and providers are numerous and can be found via a Google search.

  2. Upload the certificate to AWS using the aws console command. The command structure is:

    aws iam upload-server-certificate --server-certificate-name CERTIFICATE_NAME --certificate-body "file://PATH_TO_CERTIFICATE.crt" --private-key "file://YOUR_PRIVATE_KEY.pem" --certificate-chain "file://YOUR_CERTIFICATE_CHAIN.ca-bundle" --path /cloudfront/
    
  3. Create an Elastic Beanstalk application. Proceed through the setup process. Once the application is setup, go to Configuration -> Network Tier -> Load Balancing and click the gear icon.

  4. Select Secure listener port as 443. Select Protocol as HTTPS. Select the CERTIFICATE_NAME from step 2 for SSL certificate ID. Save the configuration.

  5. Go to your Console. Click EC2 Instances. Click Load Balancers. Click through the load balancers. Click Instances and scroll down to see the EC2 instances assigned to that load balancer. If the EC2 instance has the same name as your Application URL (or something close), take note of the DNS Name for the load balancer. It should be in the format awseb-e-...

  6. Go back to your Console. Click CloudFront. Click Create Distribution. Select a Web distribution.

  7. Set up the distribution. Set your Origin Domain Name to the load balancer DNS name you found in step 5. Set the Viewer Protocol Policy to Redirect HTTP to HTTPS. Set Forward Query Strings to Yes. Set Alternate Domain Names (CNAMEs) to the URL(s) you want to use for your application. Set SSL Certificate to the CERTIFICATE_NAME you uploaded in step 2. Create your distribution.

  8. Click on your distribution name in CloudFront. Click Origins, select your origin, and click Edit. Ensure your Origin Protocol Policy is Match Viewer. Go back. Click Behaviors, select your origin, and click Edit. Change Forward Headers to Whitelist and add Host. Save.

  9. Go to your Console. Click Route 53. Click Hosted Zones. Click Create Hosted Zone. Set up your domain name. Once set up, click Create Record Set. Enter your A record. Select Alias as Yes. Your Alias Target is your CloudFront distribution. Save the record.

  10. Set up your nameservers for your domain to point to the Route 53 nameservers. Wait for everything to propagate, which could be a few hours. Go to your URL. You will be automatically redirected to HTTPS.

  11. "But wait, my links don't go to HTTPS!?" You need to handle the X-Forwarded-Proto header that CloudFront will pass. For Laravel 4, follow this guide. For Laravel 5, run this:

    php artisan make:middleware EB_SSL_Trust
    

And then add this to the EB_SSL_Trust file:

    public function handle($request, Closure $next)
    {
        $request->setTrustedProxies( [ $request->getClientIp() ] );
        return $next($request);
    }

And add this to your App\Http\Kernel.php file:

    protected $middleware = [
        ...
        'App\Http\Middleware\EB_SSL_Trust',
        ...
    ];

Note: All your assets, such as CSS, JS or images, need to be sent over HTTPS. If you use Laravel to create these links, use secure_asset() to create the HTTPS URL in your View.

Adam Link
  • 2,783
  • 4
  • 30
  • 44
9

The use of filters has been deprecated in Laravel 5.1.*. This is a perfect job for a MiddleWare.

Create a Middleware and in the handle section put

public function handle($request, Closure $next)
{
    if(! $request->secure()) {
        return redirect()->secure($request->path());
    }
    return $next($request);
}

Then simply register your middleware in your Kernel.php and use it with your routes or controllers.

prog_24
  • 771
  • 1
  • 12
  • 26
7

Using .htaccess Apache for laravel 4.2.X

Original File

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes...
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Edit File /public/.htaccess

 <IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes...
    RewriteCond %{HTTPS} off 
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] 


    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>
whoan
  • 8,143
  • 4
  • 39
  • 48
urielOn
  • 41
  • 1
  • 2
  • 2
    But this does not remove the trailing slash. Perhaps this rule is more appropriate: `RewriteRule ^(.*[^/]|)/*$ https://%{HTTP_HOST}/$1 [L,R=301]`. When it is not secure (with or without trailing slash), it redirects properly. It would also be necessary maintain the original redirection, to be matched when the protocol is correct but trailing slash exists. – seus Feb 16 '15 at 13:49
  • Just remove `# Redirect Trailing Slashes... RewriteRule ^(.*)/$ /$1 [L,R=301]` – GetoX Jul 22 '20 at 12:46
5

Combining previous answers and updating for Laravel 4.2:

Route::filter('secure', function () {
    if (! Request::secure()) {
        return Redirect::secure(
            Request::path(),
            in_array(Request::getMethod(), ['POST', 'PUT', 'DELETE']) ? 307 : 302
        );
    }
});
Route::when('*', 'secure');
bishop
  • 37,830
  • 11
  • 104
  • 139
  • 1
    FYI, `Route::filter` code goes in **filters.php** and `Route::when ` code goes in **routes.php**. I just wanted to secure one section of my site so I added `Route::when('admin/*', 'secure');` – Justin Sep 25 '14 at 22:14
3

If you want to redirect to the same URL but using https, you should use Request::getRequestUri() instead of Request::path():

App::before(function($request)
{
    if( ! Request::secure())
    {
         return Redirect::secure(Request::getRequestUri());
    }
});
ggonzal
  • 129
  • 1
  • 3
2

This worked for me in Apache 2.4

I changed .htaccess in Laravel's root folder

From <IfModule mod_rewrite.c> RewriteEngine On RewriteRule ^(.*)$ public/$1 [L] </IfModule>

To <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteRule ^(.*)$ public/$1 [L] </IfModule>

varun sharma
  • 1,017
  • 15
  • 19
1

If you have a problem, where for some reason Request::secure() returns false, even when the url is https, it could be because $_SERVER['HTTPS'] value doesn't exist.

This is a workaround:

App::before(function ($request){
    // Force https
    if(!Request::secure() && array_get($_SERVER, 'SERVER_PORT') != 443){
        return Redirect::secure(Request::path());
    }
});
Mārtiņš Briedis
  • 17,396
  • 5
  • 54
  • 76
1

I've had a problem with forcing ssl while doing POST request. It would always redirect to GET. This happens because Redirect::secure() is by default using a 302 redirect.

To make sure your POST request are redirected properly, use something like

return Redirect::secure("your/path/here", 307)

This will make sure your request will keep original request method after redirect occurs.

MikeWu
  • 3,042
  • 2
  • 19
  • 27
1

I don't understand about HTTP and HTTPS in detail, so I'm sorry if this answer isn't very good.

It's my understanding that there is an issue that even when client and (client specified) server are using HTTPS, Request::secure() can return false because your application may be running on a different server, which is possibly not receiving a https request.

I'm hosting my laravel app in heroku and it seems it does that. My guess is that the primary (client specified) server is a load balancer and when the request is forwarded, it arrives at the other server as a normal HTTP request.

When such forwarding can happen, you should not just check for Request::secure() to be true. I was instructed (by someone in #laravel @ irc.freenode.com) to also check Request::server('HTTP_X_FORWARDED_PROTO') to see if it's equal to 'https'.

So if you intend to follow the other advice in here and perform a redirect in case of non-secure, try checking for this server parameter too.

  • You're absolutely right! Also: why doing this logic in the application, instead of the front server conf? – Luis Masuelli Aug 26 '14 at 22:17
  • @LuisMasuelli In my case, it's because idk how to configure heroku properly. When all I had as my local apache server, it was all done through apache's configuration. I'm yet to learn more about using heroku's services. – Pedro Henrique A. Oliveira Aug 26 '14 at 22:47
1

For laravel 5.1 you should use given code in App\Http\Providers\RouteServiceProvider@boot

$router->filter('force.ssl', function () {
      if ( ! request()->secure() ) {
           return redirect()->secure(request()->path());
      }
});

Now you can use this in routes file.

Route::group(['before' => 'force.ssl'], function () {
    // Routes here
});

you can also add ['before' => 'force.ssl'] in $router->group() in

App\Http\Providers\RouteServiceProvider@map
nyedidikeke
  • 6,899
  • 7
  • 44
  • 59
Muhammad Abdullah
  • 1,042
  • 7
  • 10
0

If behind a proxy and Request::secure() is not working.

App::before( function( $request )
{
  // set the current IP (REMOTE_ADDR) as a trusted proxy
  Request::setTrustedProxies( [ $request->getClientIp() ] );
});
0

Combining previous answers to use constants and methods that are available in Laravel 4.2.

routes.php

Route::when('*', 'secure');

filters.php

use Illuminate\Http\Response as IlluminateResponse;

Route::filter('secure', function ()
{
    if ( ! Request::secure() && Request::getPort() != 443)
    {
        return Redirect::secure(
            Request::path(),
            in_array(Request::getMethod(), ['POST', 'PUT', 'DELETE'])
                ? IlluminateResponse::HTTP_TEMPORARY_REDIRECT
                : IlluminateResponse::HTTP_FOUND
        );
    }
});
0

If you have to use Laravel 4 itself to handle the redirecting (like me), I'd go for the following setup (explanation as comments in the code):

Route filter:

// app/filters.php
Route::filter('ssl.force', function()
{
    if(App::environment('production') && !Request::secure())
    {
        // don't set a session cookie when redirecting to another scheme to 
        // avoid dropping the session when switching scheme
        Config::set('session.driver', 'array');
        // preserve query string while redirecting by using fullUrl()
        // instead of Redirect::secure + Request::path()
        $url = str_replace('http://', 'https://', Request::fullUrl());
        return Redirect::to($url, 302, array(), true);
    }

    // secure cookies for https
    Config::set('session.secure', Request::secure());
});

Then apply the filter as a before filter to your route or route group. eg:

// app/routes.php
Route::group(array('before' => 'ssl.force'), function () {
    // SSL routes
});
Jones03
  • 1,207
  • 2
  • 12
  • 30