Laravel and ngrok: url domain is not correct for routes and assets

11.5k views Asked by At

My setup:

  • Homestead on Mac OSX with multiple sites configured
  • I have one site setup using domfit.test as the local domain (auto mapped using hostsupdater)

My problem:

If I vagrant ssh, and then share domfit.test I get a random generated ngrok url as you'd expect (http://whatever.ngrok.io), however when I access this URL all my resources / routes are being prefixed with http://domfit.test/ (http://domfit.test/login for instance)

I've tried the following:

  • Setting APP_URL as the ngrok URL
  • php artisan config:clear
  • php artisan cache:clear
  • {{ url('login') }}
  • {{ route('login') }}

My understanding is that url() should return the actual URL that the browser requested (rather than using APP_URL) but it always returns domfit.test.

If I rename my site in Homestead.yaml (for example to newdomfit.test) and re-provision then this is the domain that url() and route() uses, regardless of my APP_URL. So the Homestead.yaml seems to be forcing that domain. Which begs the question - how are you meant to actually use the share functionality?

I'm new to Laravel so I am not sure if all of this is expected behavior and I am misunderstanding something?

I just want my links and resources in templates to work for local (domfit.test), shared (ngrok) and eventually production with the same piece of code. My worry is I will have to change all of my route() or url() references when I attempt to put this website live.

EDIT BELOW

OK I've just tried again. Changed APP_URL for ngrok:

Searched my entire codebase for domfit.test, and only some random session files seem to have references:

code/domfit/storage/framework/sessions/

APP_NAME=DomFit
APP_VERSION=0.01
APP_ENV=local
APP_KEY=XXXX
APP_DEBUG=true
APP_URL=http://04b7beec.ngrok.io

Then in my Controller I have it doing this for some simple debugging:

echo(url('/login'));
echo(route('login'));
echo($_SERVER['HTTP_HOST']);
echo($_SERVER['HTTP_X_ORIGINAL_HOST']);

If I use the ngrok URL the output I get is:

http://domfit.test/login
http://domfit.test/login
domfit.test
04b7beec.ngrok.io

I don't understand how $_SERVER['HTTP_HOST'] is returning the wrong url?

It looks like it could be related to this: https://github.com/laravel/valet/issues/342

ANOTHER EDIT

It looks like it has to do with Homestead's share command:

function share() {
if [[ "$1" ]]
then
    ngrok http ${@:2} -host-header="$1" 80
else
    echo "Error: missing required parameters."
    echo "Usage: "
    echo "  share domain"
    echo "Invocation with extra params passed directly to ngrok"
    echo "  share domain -region=eu -subdomain=test1234"
fi

}

Which passes the option -host-header to ngrok which according to their documentation:

Some application servers like WAMP, MAMP and pow use the Host header for determining which development site to display. For this reason, ngrok can rewrite your requests with a modified Host header. Use the -host-header switch to rewrite incoming HTTP requests.

If I use ngrok without it, then the website that gets displayed is a different one (because I have multiple sites configured in Homestead) - so I'm still not sure how to get around this. For the time being I could disable the other sites as I'm not actively developing those.

5

There are 5 answers

0
Mladen Janjetovic On

This is what I came up for my <base> tag ngrok issue:

<base href="{{request()->isSecure() ? 'https' : 'http'}}://{{ $_SERVER['HTTP_X_ORIGINAL_HOST'] ?? request()->getHttpHost() }}">
0
dev_mustafa On

I has similar issue and resolved it as below

In AppServiceProvider.php add below check inside boot()

if (!empty( env('NGROK_URL') ) && $request->server->has('HTTP_X_ORIGINAL_HOST')) {
            $this->app['url']->forceRootUrl(env('NGROK_URL'));
        }

Also in your .env add below entry

NGROK_URL=xxxx.ngrok.io

Full doc of the solution (link)

0
CupOfSalt On

In your app.config and .env file, try changing it to:

'url' => env('APP_URL', 'http://localhost'),

7
patricus On

Update for ngrok 3.0+

ngrok 3.0 stopped using the X-Original-Host header and started using the X-Forwarded-Host header.

Therefore, if using ngrok 3.0+ with TrustedProxies set to trust all proxies (protected $proxies = '*';), then there should be nothing else that needs to change.

However, if not using TrustedProxies, all the below information is still relevant, just replace any references of HTTP_X_ORIGINAL_HOST with HTTP_X_FORWARDED_HOST.

For ngrok < 3.0

Even though you're going to the ngrok url, the host header in the request is still set as the name of your site. Laravel uses the host header to build the absolute url for links, assets, etc. ngrok includes the ngrok url in the X-Original-Host header, but Laravel doesn't know anything about that.

There are two basic solutions to the issue:

  1. update the request with the proper server and header values, or
  2. use the forceRootUrl() method to ignore the server and header values.

TrustedProxies and Forwarded Host

If you're using TrustedProxies (default in Laravel >= 5.5), and you have it configured to trust all proxies (protected $proxies = '*';), you can set the X-Forwarded-Host header to the X-Original-Host header. Laravel will then use the value in the X-Forwarded-Host header to build all absolute urls.

You can do this at the web server level. For example, if you're using apache, you can add this to your public/.htaccess file:

# Handle ngrok X-Original-Host Header
RewriteCond %{HTTP:X-Original-Host} \.ngrok\.io$ [NC]
RewriteRule .* - [E=HTTP_X_FORWARDED_HOST:%{HTTP:X-Original-Host}]

If you prefer to handle this in your application instead of the web server, you will need to update the Laravel request. There are plenty of places you could choose to do this, but one example would be in your AppServiceProvider::boot() method:

public function boot(\Illuminate\Http\Request $request)
{
    if ($request->server->has('HTTP_X_ORIGINAL_HOST')) {
        $request->server->set('HTTP_X_FORWARDED_HOST', $request->server->get('HTTP_X_ORIGINAL_HOST'));
        $request->headers->set('X_FORWARDED_HOST', $request->server->get('HTTP_X_ORIGINAL_HOST'));
    }
}

Not Using TrustedProxies

If you're not using TrustedProxies, you can't use the .htaccess method. However, you can still update the server and headers values in your application. In this case, you'd need to overwrite the Host header:

public function boot(\Illuminate\Http\Request $request)
{
    if ($request->server->has('HTTP_X_ORIGINAL_HOST')) {
        $request->server->set('HTTP_HOST', $request->server->get('HTTP_X_ORIGINAL_HOST'));
        $request->headers->set('HOST', $request->server->get('HTTP_X_ORIGINAL_HOST'));
    }
}

Using forceRootUrl()

If you don't want to modify any headers or the Laravel request, you can simply tell the URL generator what root url to use. The URL generator has a forceRootUrl() method that you can use to tell it to use a specific value instead of looking at the request. Again, in your AppServiceProvider::boot() method:

public function boot(\Illuminate\Http\Request $request)
{
    if ($request->server->has('HTTP_X_ORIGINAL_HOST')) {
        $this->app['url']->forceRootUrl($request->server->get('HTTP_X_FORWARDED_PROTO').'://'.$request->server->get('HTTP_X_ORIGINAL_HOST'));
    }
}
3
David Martínez On

You can force the protocol with the last @patricus solution using this:

$this->app['url']->forceScheme('https');