[SOLVED] Twig base_url() returns port 80 when used over HTTPS connection

I have a site which I just HTTPS-enabled … when I access the site now over HTTPS and use the base_url() twig plugin, this plugin returns the value

https://mysite.com:80

Note the combination of “https” and port 80, which seems pretty odd.

Any idea if this is something I’m doing wrong or am I running into a bug here?

Thanks a lot for any insights.

Can you dump the value of $_SERVER please?

@TheLobos, I came across something similar awhile back. Slim’s methods were giving me URLs like https://example.com:80. If you are using some sort of SSL proxy, CloudFlare, or something similar between the client and server then the actual request going into slim might be over port 80 even though the client to proxy connection is over SSL and port 443.

1 Like

Hi,

this is exactly what’s happening; this server his behind a CloudFlare firewall (for lack of a better word). The problem this is causing is that since the connection is established as SSL subsequent URLs for CSS/JS include files are generated with that “:80” port specification and the browser then refuses to load them because it doesn’t want to mix insecure content with a SSL connection.

I can probably hack some into the Uri class to hardcode port 443, but is there a proper/smart way to force Slim to generate 443 URLs through some initialization parameters or something of that sort?

@akrabat I can still provide $_SERVER if you think this would be helpful but since @tflight diagnosed the underlying issue, I haven’t done so yet.

Thanks for any help; I totally appreciate you pinpointing the underlying issue …

@TheLobos I would set the URI manually when constructing the Twig Extension.

(see: https://github.com/slimphp/Twig-View/blob/master/src/TwigExtension.php#L23)

1 Like

I like the way @akrabat suggests better than what I did. I just added a new attribute to the $view when defining it in the container. $view['appUri'] = 'https://example.com'; (Or actually, I put the value in a settings file so it works correctly across multiple environments.)

1 Like

Thank you very much @tflight and @akrabat.

I thought this would be easy to fix but so far a solution is eluding me. Here’s what I’ve done:

    $view = new \Slim\Views\Twig ($paths, $settings['view']['twig']);
    $uri = $container->get('request')->getUri();
    if ($settings['forceHttps'] && $uri->getScheme() == 'https' && $uri->getPort() != 443) {
        $uri = new \Slim\Http\Uri($uri->getScheme(), $uri->getHost(), 443, $uri->getPath(), $uri->getQuery(), $uri->getFragment());
    }
    $view->addExtension (new \Slim\Views\TwigExtension($container->get('router'), $uri));

So, I check whether the condition of ‘https’ and port 80 applies, and if so, I create a new $uri based upon the parameters of the original $uri but hardcode port 443. This new $uri is then used to create the Slim\Views\TwigExtension.

However, the URLs generated are still generated as

https:mysite.com:80/

Am I overlooking something stupid/simple here?

Is this a multi-tenant app or something you intend to distribute? If not, then perhaps the way I’m doing it might work out better. My approach is hardcoding it a bit more, but still works across multiple environments.

Most of the time I’m using path_for in my Twig views, and since that just spits out a relative URL, I’m good to go without any modifications. When I need to overcome this issue is when I need a full URL, such as in email templates or sometimes providing callback URLs to external services.

In those cases, I’ve added an entry to my settings. 'env_uri => 'https://example.com' My settings file is unique to each of my environments (local, staging, production) so that value can conveniently change. Then in my DI container I add this variable to the Twig environment.

$view = new Slim\Views\Twig(...
$view['env_uri'] = $c->get('settings')['env_uri'];

Now, in my views in cases where I need more than just the relative path, like email templates, I concatenate the Twig variable with path_for.

<a href="{{ env_uri }}{{ path_for(...) }}">link</a>

This approach works well for me. I use this technique for more than just this issue though. I also use it to load in callback URLs, public keys, and other variables unique to my environments that I need “globally” in Twig.

I first thought of doing it this way, but the problem is that:

  1. I indent to eventually distribute this and I’d like to avoid requiring people to hardcode their domain path.
  2. This requires template changes which will mean that the codebase will not be using the default slim url generation routine anymore but require this addon.

So while I can use your solution, I’d like to have one where I can fix this at the core (like I attempted and for which I’m a bit perplexed by it doesn’t work).

Thanks for your help though.

I’ll have a look at the method you are using a little later today and see if I can get it working in the environment where I came across this issue.

I tried out your solution on one of my servers where I saw the same behavior. Prior to your implementation, $uri->getBaseURL() would respond with https://example.com:80, after implementing your fix I get https://example.com, just as we’d expect/hope.

So I’m not sure why that isn’t working for you, aside from a different server setup. I’d var_dump/inspect the old $uri and the new $uri to see what is going on.

1 Like

Thank you, I finally figured out what was wrong. There was a place buried deep inside my app code where I was accessing the original URI which had been stored in the app container; my original fix was correct but not applied everywhere were it needed to be.

Thanks a lot for your help.

1 Like

@TheLobos What is the final solution (in code)?
@akrabat Rob, if I want to set the URI manual. Where would I do this? Possible to do this in de bootstrap/app.php?

I put a flag called “forceHttps” into my settings file (since this is a problem which only appears on certain cloudflare configurations) and wrote a function like this:

public static function getUrl ($routeName, array $data = [], array $queryParams = [])
{
    $router   = self::getRouter();
    $settings = self::getSettings();
    $uri      = self::getUri();
    $scheme   = $uri->getScheme();
    $port     = $uri->getPort();

    if ($settings['forceHttps'] && $port != 443) {
        $uri = new \Slim\Http\Uri('https', $uri->getHost(), 443, $uri->getPath(), $uri->getQuery(), $uri->getFragment());
    }

    return (string)($uri->withPath($router->pathFor($routeName, $data, $queryParams)));
}

Hope this helps.

@TheLobos hanks Wintermute. Will give it a try tonight.

If I understand correct; you put the code in the settingsfile (bootstrap/app.php) and instead of

{{ base_url }} in Twig you are using {{ getUrl }} ?

Not quite. The actual config option/flag is in the config file … in my setup this is called settings.php …

I use Twig so the getUrl() function (which is defined in a central utility class) is wrapped into a Twig plugin so that I can construct such URLs on the template level … if you’re not using Twig (or some other template framework) then you can just use the getUrl() function directly.

I actually solved this issue in a slight different manner. Inspired by akrabat’s rka-scheme-and-host-detection-middleware project, I created a middleware that modify the port on the fly depending on the URL scheme:

<?php
namespace App\Middleware; // Namespace to be adapted.

use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Uri;
use Interop\Container\ContainerInterface;

class SchemeMiddleware
{
    public function __construct(ContainerInterface $ci){}

    public function run(Request $request, Response $response, $next)
    {
        if (!$next) {
            return $response;
        }

        $uri = $uri->withPort($uri->getScheme() === 'https' ? 443 : 80);

        return $response = $next($request->withUri($uri), $response);
    }
}

And then you need to declare this middleware last so that it’s loaded first:

// $app->add(...); other middlewares...
$app->add('App\Middleware\SchemeMiddleware:run'); // Namespace to be adapted.

Hi Kalvn,

In the Uri.php is the getBaseUrl() function.
Is it possible to adjust the code here and would it work?

/**
     * Return the fully qualified base URL.
     *
     * Note that this method never includes a trailing /
     *
     * This method is not part of PSR-7.
     *
     * @return string
     */
public function getBaseUrl()
    {
        $scheme = $this->getScheme();
        $authority = $this->getAuthority();
        $basePath = $this->getBasePath();

        if ($authority && substr($basePath, 0, 1) !== '/') {
            $basePath = $basePath . '/' . $basePath;
        }

// if ($scheme === 'https' ){
// Do something
}

        return ($scheme ? $scheme . ':' : '')
            . ($authority ? '//' . $authority : '')
            . rtrim($basePath, '/');
    }

I guess it would but it must not be done. Changing the code of an external library or any kind of dependency mustn’t be done as it would be overridden when you update these dependencies.