HerokuにデプロイしたLaravelアプリのアセットのURLをhttpsにする手順


HerokuではSSLの処理をロードバランサーで行っているので、Dyno上のLaravelアプリケーションはデフォルト設定ではhttpで動いていると判定されアセットのURLがhttpになってしまします。

そのまま表示すると以下のようにブラウザの警告が出てしまいます。

Herokuのロードバランサーまわりについては、公式ドキュメントのHTTP Routingで説明されています。

Laravelで、ロードバランサー経由のhttpsリクエストを判定するには、公式ドキュメント「HTTP Requests」の「Configuring Trusted Proxies」の「Trusting All Proxies」に記述されている通りApp\Http\Middlewar\TrustProxiesクラスのproxiesの値にロードバランサーのIPアドレスをセットします。
Herokuの場合、そもそもロードバランサーのIPアドレスは判らないのですし、Web DynoにはグローバルIPアドレスは割あたってない(はず)ので、すべてのプロキシからのリクエストを信頼する「*」をセットします。

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies = '*'; // <== これ

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

実装としてはこれだけなのですが、少し仕組みを解説してみます。

ミドルウェアApp\Http\Middlewar\TrustProxiesは、Fideloper\Proxy\TrustProxiesを継承しています。

$proxiesに「*」をセットした場合は、setTrustedProxyIpAddressesToTheCallingIpメソッドが呼ばれますが、このメソッドではリクエストのサーバー環境変数REMOTE_ADDRをそのまま信頼されるアドレスとして保存します。

// https://github.com/fideloper/TrustedProxy/blob/master/src/TrustProxies.php
    protected function setTrustedProxyIpAddresses(Request $request)
    {
        $trustedIps = $this->proxies ?: $this->config->get('trustedproxy.proxies');

        // Trust any IP address that calls us
        // `**` for backwards compatibility, but is deprecated
        if ($trustedIps === '*' || $trustedIps === '**') {
            return $this->setTrustedProxyIpAddressesToTheCallingIp($request);
        }

        // Support IPs addresses separated by comma
        $trustedIps = is_string($trustedIps) ? array_map('trim', explode(',', $trustedIps)) : $trustedIps;

        // Only trust specific IP addresses
        if (is_array($trustedIps)) {
            return $this->setTrustedProxyIpAddressesToSpecificIps($request, $trustedIps);
        }
    }

    // 中略

    private function setTrustedProxyIpAddressesToTheCallingIp(Request $request)
    {
        $request->setTrustedProxies([$request->server->get('REMOTE_ADDR')], $this->getTrustedHeaderNames());
    }

続いてhttpsかどうかの判定ですが、LaravelのIlluminate\Http\Requestクラスのsecureメソッドに実装されています。

secureメソッドは、親クラスのSymfony\Component\HttpFoundation\RequestクラスのisSecureメソッドを呼んでおり、まずisFromTrustedProxyメソッドで信頼済みのプロキシであることを確認しています。このとき、上記の通り「*」の場合はミドルウェアTrustProxiesでリクエストが来たアドレスを信頼済みプロキシとしてセットしているので必ずtrueが返ります。

次にX-Forwarded-Protoヘッダーの値をチェックして、httpsぽい値がセットされていればtrueを返します。
Herokuのロードバランサーは、httpsからどうかの情報をX-Forwarded-Protoヘッダーに追加してバックエンドのWebアプリケーション サーバーに渡しているためtrueと判定されます。確認したところ値は「https」でした。
(他のロードバランサーでも一般的にX-Forwarded-Protoを付与されるので、この設定はHeroku以外でも利用可能です)

// https://github.com/symfony/http-foundation/blob/master/Request.php#L1121

    public function isSecure()
    {
        if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) {
            return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
        }
        $https = $this->server->get('HTTPS');
        return !empty($https) && 'off' !== strtolower($https);
    }

あとは、Viewのヘルパー関数assetからIlluminate\Routing\UrlGeneratorassetメソッドが呼ばれると’Symfony\Component\HttpFoundation\RequestクラスのgetSchemeメソッドが呼ばれ、その中で上記のisSecure`メソッドが呼び出されるという流れになります。

, ,