Laravel Redirect All Requests To HTTPS - laravel-4

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
}

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');
}

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.

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:
Obtain an SSL certificate. Guides and providers are numerous and can be found via a Google search.
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/
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.
Select Secure listener port as 443. Select Protocol as HTTPS. Select the CERTIFICATE_NAME from step 2 for SSL certificate ID. Save the configuration.
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-...
Go back to your Console. Click CloudFront. Click Create Distribution. Select a Web distribution.
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.
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.
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.
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.
"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.

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.

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>

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');

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());
}
});

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>

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());
}
});

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.

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.

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

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() ] );
});

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
);
}
});

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
});

Related

Laravel middleware is returning unauthenticated in production server from sub directory

I am sending an API request to get user detail after login
I have a project in which I am running APIs only, I placed this project in sub-directory which has name api. so front-end is running on the root which is built in react JS.
also, I have removed the API prefix from RouteServiceProvider because I don't want to call domain.com/api/api
Route:
Route::group(['namespace' => 'Auth'], function(){
...
Route::post('login','LoginController#login'); // Running successfully
Route::middleware('auth:api')->get('/user', 'LoginController#user'); // Returning 401 unauthenticated
});
RouteServiceProvider
protected function mapApiRoutes()
{
Route::middleware('api')
->as('api.')
->namespace($this->namespace."\\API")
->group(base_path('routes/api.php'));
}
Tried solutions
1) I have placed Passport::withoutCookieSerialization(); inside boot method in AppServiceProvider
2) I have placed the Passport::$ignoreCsrfToken = true; inside boot method in AuthServiceProvider
Thanks in advance
I have found the solution myself.
I have added these lines to .htaccess file placed in the root of Laravel project (not public).
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Laravel 5.6 signed url won't work in APP_ENV=production

I've setup two routes
Route::get('/bugreport', 'SomeController#create')
->middleware('signed')
->name('bugreport');
Route::get('/getlink', function() {
return dd(\URL::signedRoute('bugreport', ['data' => 3]));
});
When APP_ENV=local I can visit /getlink then hit the resulting url and have it show. When APP_ENV=production (the actual physical environment has not changed when changing the variable) this no longer works... stating invalid signature. Any suggestions?
UPDATE:
We do have... which might be part of it
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
if (!config('app.is_local')) {
URL::forceScheme('https');
}
}
Note: removing the above code actually does fix this issue, but then it breaks e.g. login... so either need to understand why, or this isn't an option :(.
Update Update:
The environment is heroku and the .htaccess has (as per https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls)
#### FORCE SSL
## see - https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls
RewriteCond %{HTTP_HOST} !=localhost
# If we receive a forwarded http request from a proxy...
RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR]
# ...or just a plain old http request directly from the client
RewriteCond %{HTTP:X-Forwarded-Proto} =""
RewriteCond %{HTTPS} !=on
# Redirect to https version
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
### End Force SSL
I had a similar problem - it can be solved very easy if you use the TrustedProxy Middleware.
If you are on Heroku or AWS behind Load Balancers, and you have SSL offloaded to load balancers, you might want to Trust all proxies, so that Laravel's Request can detect that you are actually on a SSL connection.
Then Signed URL Validation will work.
You can see the doku here:
Laravel 5.7 https://laravel.com/docs/5.7/requests#configuring-trusted-proxies
Laravel 5.6 https://laravel.com/docs/5.6/requests#configuring-trusted-proxies
Update: Use the accepted answer instead.
I finally isolated this down to the Heroku changing the protocol on the requests, so that even when the client makes a request in https laravel will always receive it as http in the request object. The workaround was
if (!config('app.is_local') && strpos($request->url(), 'http:') !== false)
{
$url = str_replace('http:', 'https:', $request->fullUrl());
$request = Request::create($url);
}
if (!$request->hasValidSignature()) {
return abort(403);
}
This was sufficient for my needs.

Laravel - Catch specific public folder route

Sorry if this sounds so basic.
I've encoutered a scenario where I need to add certain checks when user access certain file in public folder.
With default setting, you can access specific file in public directory directly by typing its url in the browser: Sorry this is misleading.
You can access an image with:
localhost/img/banner.jpg
But I can't catch it with:
Route::get('/img/banner.jpg', function(){
//checks goes here
});
How it should be done?
You should create controller for serve images(files) and disallow direct access if you need control. On example:
Route::get('/file/{name}', function ($name) {
// Your check and return file with correct response headers
}
Or best way to create controller for that.
https://laravel.com/docs/5.6/filesystem . Check laravel Docs to see how manage multiple file storages.
I just found another way without using filesystem.
In .htaccess inside public folder, add these:
RewriteCond \banner.jpg$ !-d
RewriteRule ^ index.php [L]
Which will let laravel handle the route then I can catch it with:
Route::get('/img/banner.jpg', function(){
//checks goes here
});

AJAX calls to fail with mixed content HTTP/HTTPS errors, can't force HTTPS

I have a single EC2 instance running behind ELB, for which I utilized AWS Certificate Manager (ACM) to enable HTTPS.
As suggested in a few places, I mapped both HTTP (Port 80) and HTTPS (443) to my instance's HTTP (Port 80).
I also enabled forcing secure connections by adding these lines to .htaccess file:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} ^http$
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
All that is working beautifully. All pages display secure content as expected, no warnings in the Console about mixed content.
Only on pages where I make an AJAX call I get an error about mixed content and only in Chrome. FireFox works as expected but Console still shows an error.
Error (FireFox):
Content Security Policy: Upgrading insecure request 'http://example.com/myapi/get_company/?code=abcd&limit=8' to use 'https'
Error (Chrome):
Mixed Content: The page at 'https://example.com/mypage' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://example.com/myapi/get_company/?code=abcd&limit=8'. This request has been blocked; the content must be served over HTTPS.
I opened chrome://net-internals/ and from what I can see there are two calls the first one receives HTTP 301 Permanent Redirect and the second one fails because the redirect points to an HTTP page, not HTTPS.
AJAX call looks like this:
$url = "/myapi/get_company?code=" + e;
apiJson = $.ajax({
url: $url,
dataType: 'json',
data:{
limit:8
},
success: function(data) { //Some code here },
error: function(jqXHR, textStatus, errorThrown) { //Some code here }
});
I also tried giving the full URL including the protocol but that didn't make a difference.
Update .htaccess config
RewriteCond %{HTTPS} !=on --> Is not needed
update the .htaccess file config to
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]
Apart from changing .htaccess file you also need to ensure, once a page loads using HTTPS scheme, the ajax calls made from the page should not be on HTTP. The error you are seeing are browser errors and are isolated from how your elb is configured.
I found a solution and now posting an answer to my own question. Don't ask me how I figured this out because I won't be able to give you a definite answer but this is what did it:
$url = "/myapi/get_company?code=" + e;
changed to:
$url = "/myapi/get_company/?code=" + e;
Notice the trailing slash, right before the parameters. The actual file handling the AJAX calls on the server side is here:
/myapi/get_company/index.php
Check if the problem is on the server, not on the client. Your AJAX call is to an API endpoint; does that API endpoint return an HTTP redirect when it should return an HTTPS redirect?

Unable to run the app on hosted server

I have just installed Ci framework and tried on my local some basics according to the tutorial.
http://ellislab.com/codeigniter/user-guide/tutorial/static_pages.html
On local machine, everything was OK and page was displayed.
Than I upload project to the server (I am using wedos web hosting where I already have som web which is working correctly) but this is not working for the application which I copied it here. When I type the URL I got 404 error.
On my webhosting service I have www folder where i put my index.html file. Than when i type www.mydomain.eu I get this index.html file. Than I have subfolder www/folder/index.html Than when i type www.mydomain.eu/folder i get this page so it is ok.
Than i have www/folder2/here i unzip CodeIgniter framework and when i type www.mydomain.eu/folder2, 404 Page Not Found appears. The error is not general error from browser but generated from the CI framework.
I have created my own controler in application/controllers/mycontroller.php
<?php
class Mycontroller extends CI_Controller {
public function view($page = ‘enter_form’)
{
$data[‘title’] = ucfirst($page); // Capitalize the first letter
$this->load->view(‘templates/header’, $data);
$this->load->view(‘pages/’.$page, $data);
$this->load->view(‘templates/footer’, $data);
}
}
And I have following structure of views:
views/pages/enter_form.php
views/templates/header.php and footer.php
And the following settings:
1) $config[‘base_url’] = ‘’; but I have tried ‘http://mydomain.eu/’ and ‘http://mydomain.eu/www/’ or ‘http://mydomain.eu/www/folder2/’
2) $route[‘default_controller’] = ‘mycontroller/view’;
$route[’(:any)’] = ‘mycontroller/view/$1’;
Thank you for any help
Sounds to me like there is either a configuration issue or a rewrite issue going on. Do you have a .htaccess file in your CI root folder where your index.php file lives? Seeing that you are depending on the CI to pick up any uri and reroute it to a specific route, this might be the issue. I've ran into issues with this before on different hosts. Make sure mod rewrite is enabled and make sure your .htaccess is something similar to this:
RewriteEngine on
RewriteBase /
RewriteCond $1 !^(index\.php|assets|uploads)
RewriteRule ^(.*)$ /index.php/$1 [L]
Ok, so it seems like the typo somewhere, because when I used the default CI welcome pages, it works. So I will try to find the typo myself and i will see...

Resources