Our website is hosted on a load balanced server. SSL offloading is done on the firewall, so the client is reverse proxied to the web server farm.
When the https request reaches our Laravel app, the server variable HTTPS is empty and Laravel doesn't seem to detect the https mode and generates urls (assets & routes) as:
http://www.somesite.be:443/assets/
http://www.somesite.be:443/nl-BE/about
Is there a way to configure Laravel to force the url's to generate https links?
We prefer to have a configuration solution because we have a development and staging environment that aren't running under https.
Notice:
We already tried the "trustedproxy" approach from fideloper, and this resulted in no change.
I presume that a .htaccess rewrite is not an option since htaccess rewrites are based on the same https header (we don't receive) or port (80, laravel calls port 443).
Thanks for the help.
Laravel's UrlGenerator class has a method called forceSchema, which allows you to force the schema to be used and ignore the one extracted from the request's URL. Just create a service provider SecureRoutingServiceProvider which uses Laravel's IOC to override the default generator and return an instance that forces the secure schema:
use Illuminate\Routing\UrlGenerator;
use Illuminate\Routing\RoutingServiceProvider;
class SecureRoutingServiceProvider extends RoutingServiceProvider
{
public function boot()
{
App::bind('url', function () {
$generator = new UrlGenerator(
App::make('router')->getRoutes(),
App::make('request');
});
$generator->forceSchema('https');
return $generator;
}
parent::boot();
}
}
Next we'll need to register the service provider by adding it to the providers array in app/config/app.php:
'providers' => array(
...,
'SecureRoutingServiceProvider',
)
And that's all there is to it. I've tested this code and it works fine (in Laravel 4.2).
Working on the same issue with Laravel 5 Pagination Feature. For that its not enough to just force the URL Schema in the Generator, because its using the URL associated with the Request. After digging i found a good fix.
Illuminate\Http\Request has a trustedProxies Array that is basically for this case.
I still used the SecureRoutingServiceProvider from Bogdan as a starting point to whitelist our Load Balancer.
public function boot()
{
Request::setTrustedProxies(['10.0.0.X']); // Here should be your internal LB IP
parent::boot();
}
After that it worked pretty well. Of course you should put the IP in a config/env file.
Related
I want to use Laravel maintenance mode on EC2 instance behind the load balancer because I do not want to touch AWS console for returning maintenance content.
Moreover, I want to access my app via web browser from my office while maintenance mode.
I did following and it turns into maintenance mode.
But, I can not see my app from my office although the IP at my office in the allow list.
php artisan down --allow=127.0.0.1 --allow=myip/34
Do you have any suggestions for this?
Here is my environment information-
PHP: 5.7
Laravel: 5.8
Also, I have following source code in App/Http/Middleware/TrustProxies.php
class TrustProxies extends Middleware
{
protected $proxies = '*';
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
}
Regards,
Since you're behind a load balancer you'll be receiving the ip of that load balancer rather than the client ip.
TL;DR
In your app/Http/Middleware/TrustProxies.php change the protected $proxies; line to be:
protected $proxies = '*';
Since Laravel 5.4, there is an out-of-the-box way of dealing with this: TrustedProxy. If you're using an earlier version of Laravel you can still use the package, however, you'll have to install it yourself.
Where possible, you should try and set the ip addresses of the reverse proxy, however, this isn't possible with AWS since the ip addresses of the load balancer change all the time (source: https://github.com/fideloper/TrustedProxy/wiki/IP-Addresses-of-Popular-Services#aws-elastic-load-balancers).
For more information, you can refer to the Laravel documentation Configuring Trusted Proxies or the Github page for the underlying package.
Instead of modifying your Middleware code there, it is better to put this into your configuration file. Add config/trustedproxy.php file with the following code in it:
<?php
return [
'proxies' => env('TRUSTED_PROXIES'),
];
Then add the following line to your .env file:
TRUSTED_PROXIES="*"
Also a side note: it is generally not really a good idea to trust all proxies, because people can just fake the X-Forwarded-For headers. Instead, you can put the IPs for your private IP subnet, whatever it is in your EC2 VPC. It would be one of the RFC 1918 private IP networks, by default it would be one of the 172.x.x.x subnets. You can then substitute the "*" in the code above with something like "172.16.0.0/12" or whatever your private subnet is — you can look it up in your VPC settings.
I have this Laravel App which I'm deplying to Heroku.
I have followed all of the steps until I encountered a problem relating some assets (asset('css/app.css'), for example) refering to http urls, instead of https urls.
I solved that by adding
if(config('app.env')==='production'){
\URL::forceScheme('https');
}
in the boot method of my AppServiceProvider.php file, and it worked.
But now I have encountered another http related problem that the previous code couldn't solve.
I am fetching my data using simplePaginate() function like so
public function index(Question $question){
$answers = $question->answers()->with('user');
return $answers->simplePaginate(3);
}
This code returns me a response with my 3 answers, as well as with a property called 'next_page_url'
which is, still, plain http (not https as i need it to be).
What can I do for this to be https as Heroku requires?
Heroku's load balancing setup means the indication of whether the request is HTTP or HTTPS comes from the X-Forwarded-Proto header. (Laravel also needs the X-Forwarded-For header to get the users' real IP addresses, incidentally.)
By default, Laravel doesn't trust these headers (as in a different setup it might come from a malicious client), so none of the requests will be detected as HTTPS. You can fix this by configuring the Laravel trusted proxy to trust the header.
In the default config, just setting $proxies = '*', will do the trick, and is safe on Heroku because the load balancers can't be bypassed by end users.
The correct way is to change the URL of your app to https://example.com in the configuration file (.env file as an example). Just write APP_URL=https://example.com
But, when you use Heroku - their balancers can route your requests to https://yourDomain.com to your application over HTTP. So, the Laravel app receives the request to http://yourDomain.com and decides that you need a response with HTTP links.
As #seejayoz said you need to configure trusted proxies list for your app.
I think you can use withPath (or setPath alias) :
$pagi=$answers->simplePaginate(3);
$pagi->withPath("https://link/xxx/");
return $pagi;
I'm having issues with a new Laravel app behind a load balancer.
I would like to have Laravel do the Auth middleware 302 redirects to relative path like /login instead of the http://myappdomain.com/login is actually doing.
I only see 301 redirects in the default .htaccess Laravel ships which makes me believe the behavior is right within Laravel, am I wrong?
Can someone point me in the right direction?
If you need to properly determine whether a request was secure when behind a load balancer you need to let the framework know that you're behind a proxy. This will ensure that the route() and url() helpers generate correct URLs and remove the need to create relative redirects which are both not 100% supported by browsers and also won't work properly when serving a webpage from a sub-path.
This is what we use to solve this problem and it's working so far for us:
.env
LOAD_BALANCER_IP_MASK=aaa.bbb.ccc.ddd/xx #Subnet mask
LoadBalanced Middleware
class LoadBalanced {
public function handle($request, $next) {
if (env("LOAD_BALANCER_IP_MASK")) {
$request->setTrustedProxies([ env("LOAD_BALANCER_IP_MASK") ]);
}
$next($request);
}
}
Then put the middleware in your Kernel.php:
protected $middleware = [
LoadBalanced::class,
//.... It shouldn't matter if it's first or last as long as other global middleware don't need it
];
This is a feature available to Laravel because it is using the Symfony request as a base. How this work is that the load balancer forwards some important headers. Symfony currently understands:
protected static $trustedHeaders = array(
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);
which have information regarding the user making the request to the load balancer and the protocol used.
Also according to framework comments:
The FORWARDED header is the standard as of rfc7239.
The other headers are non-standard, but widely used
by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
Update:
Since version 5.5, the Laravel boilerplate package includes the TrustedProxy middleware which uses the fideloper/TrustedProxy package.
To have it working you need to (a) make sure it's in your $middleware array in your App\Http\Kernel class and that you place the IPs of the trusted proxies in this middleware e.g.
protected $proxies = [
'1.2.3.4'
];
I would highly recommend to explicitly specify which forwarded headers your proxy sends e.g.:
protected $headers = Request::HEADER_X_FORWARDED_AWS_ELB;
if you're using an AWS load balancer.
The reason for this is quite important in that if you are using an AWS load balancer then someone could craft a request with the 'Forwarded` header and that will be forwarded by AWS and then processed by the middleware essentially allowing users to spoof their IP host/port etc.
I have a Laravel 5.1 application that has many clients with their own unique subdomains and databases.
On app loading, my middlewares resolves the client and sets the app.url (config/app.php) accordingly. It all works great, in the browser. All urls generated by route() has the correct subdomain for current client.
But, stuff queued (Redis in my case) will always defaults to a domain URL of "localhost".
So if I send a welcome email where the text template contains route('account') it will from the queue generate a "http://localhost/account" URL. This is of course not correct.
I've found the line that probably does this, it's in the Illuminate\Foundation\Bootstrap\SetRequestForConsole class:
$url = $app->make('config')->get('app.url', 'http://localhost'); <---
As far as I can see, I can't really "hook" into anything before that.
Info: For each Queued command (closure) I have, I always send with it who the client is that's queing. That way I set the client before the queued command is fired. It loads the right database connection. But changing the default route() server name appears rather difficult!
I've experimented with extending the UrlGenerator class, but it appears that this is completely ignored for queued commands and only works on HTTP requests.
I've also tried adding this before the queues command is fired:
app('url')->forceRootUrl($client->getClientUrl());
It did not work. (but does work with HTTP requests)
Anyone here have an idea for how to set my own default domain for route() in CLI mode?
In your environment config (.env) file, add an entry:
APP_URL='http://www.example.com'
Then in your config/app.php file change the application url from localhost:
----------------
Application URL
----------------
...
...
//'url' => 'http://localhost',
'url' => env('APP_URL'),
...
These two changes worked for me when I used the database queue driver in Laravel 5.1.
I case you have sub domain and your want to make route url with sub-domain so in this case there might be problem.
I have used this in my app. config/app.php file
...
...
//'url' => 'http://localhost',
'url' => url('/'),
...
This is probably an oversight on my part, but I am wondering what's the correct way to set the default pagenate base url.
Our domain is on https://domain.com and we have set config app.url as such, but pagination still appear to use http://domain.com (note the scheme is http instead of https) when generating url in view. I am wondering how to do this without writing setBaseUrl everywhere.
(We are using Laravel 4.1)
Update: we got the same problem with Form::open as well, where is the setting that make Laravel realize we want url with https instead of http? Shouldn't it be using app.url as a base url?
Figure out the answer myself:
Since our server is behind a reverse proxy (nginx), while content is served as HTTPS, the proxying is done with HTTP, so we should set following to nginx config:
proxy_set_header X-Forwarded-Proto $scheme; //or just `https`
(same as your proxy_pass upstream block)
Then we can use Trusting Proxies feature as such:
$proxies = Config::get('app.trusted_proxies');
// trust any balancer
if ($proxies === '*')
{
$proxies = array( Request::getClientIp() );
}
// else trust an array of IPs
if ($proxies && is_array($proxies))
{
Request::setTrustedProxies($proxies);
}
(This can be added to bootstrap/start.php. Laravel use Symfony Request library, which is why we linked to their document, as this feature is not documented in Laravel docs)