📑

laravel12 + vite(inertia.js他) 環境下におけるmixed content

に公開
<!-- ページ自体は https://example.com なのに -->
<script src="http://example.com/script.js"></script>
<img src="http://example.com/image.jpg" />

これ。この状況はhttps proxy下でlaravelを包んだとき、とくにviteの環境で簡単に発生する。たとえばdev.test.catatsumuri.orgでやってみるなら

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName dev.test.catatsumuri.org

    ServerAdmin webmaster@localhost

    # リバースプロキシ設定(Laravelの開発サーバへ)
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8000/
    ProxyPassReverse / http://127.0.0.1:8000/

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    SSLCertificateFile /etc/letsencrypt/live/dev.test.catatsumuri.org/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/dev.test.catatsumuri.org/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

こういう状況でartisan serveされてると

こんな感じになる。

これのややこしいのはlaravel側ではエラーにならないのでブラウザーの開発ツール的なのを起動しないと何がおきてるのかよくわかり辛いということだ(まっしろになってる典型的なやつ)

解決法1: 強制https

これはapp/Providers/AppServiceProvider.phpに以下の設定を行う

app/Providers/AppServiceProvider.php
@@ -20,5 +20,6 @@ public function register(): void
     public function boot(): void
     {
         //
+        \URL::forceScheme('https');
     }
 }

これは問答無用でhttpsにしてしまう方法であり、まあ使えなくはないが非常にローテクな感じがある。このままだと当然ローカル開発も全てhttpsになるし、このファイルに関しては大抵commit対象になるはずなので非常に面倒くさいことになるはずだ。ifでグルグル分岐するとかいうので回避できなくもないかもしれないが、いずれにせよ非推奨ではある。しかし、簡単な手法なので最初に一度試してみる価値はあるかもしれない。

解決法2: URLを決め打ちする

これはproduction環境では非常に有効な手法であるし、むしろそのようにしておきたい。これに関しては

.env
APP_URL=https://dev.test.catatsumuri.org

だけではだめで

.env
ASSET_URL=https://dev.test.catatsumuri.org

も併せて指定する必要がある。この後

npm run build

することでassetsのurlも固定される。

解決法3: trustedProxyする

bootstrap/app.php
@@ -6,6 +6,7 @@
 use Illuminate\Foundation\Configuration\Exceptions;
 use Illuminate\Foundation\Configuration\Middleware;
 use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
+use Illuminate\Http\Request;

 return Application::configure(basePath: dirname(__DIR__))
     ->withRouting(
@@ -14,6 +15,15 @@
         health: '/up',
     )
     ->withMiddleware(function (Middleware $middleware) {
+        $middleware->trustProxies(
+            at: '*', // または ['127.0.0.1'] など具体的なIP
+            headers: Request::HEADER_X_FORWARDED_FOR |
+                Request::HEADER_X_FORWARDED_HOST |
+                Request::HEADER_X_FORWARDED_PORT |
+                Request::HEADER_X_FORWARDED_PROTO |
+                Request::HEADER_X_FORWARDED_AWS_ELB
+        );
+
         $middleware->encryptCookies(except: ['appearance', 'sidebar_state']);

こんな感じでヘッダを見て通すというのを書く。apache2であれば

<VirtualHost *:443>
    ServerName dev.test.catatsumuri.org

    ServerAdmin webmaster@localhost

    # リバースプロキシ設定(Laravelの開発サーバへ)
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8000/
    ProxyPassReverse / http://127.0.0.1:8000/
    RequestHeader set X-Forwarded-Proto "https" # これと
    RequestHeader set X-Forwarded-Port "443" # これ
# ...

というのを書いておけばいい。環境によっては便利だがサーバー設定が必要なこともあるし、これもコードが動いてしまう。productionでは2の方法で完全に解決できるのでそっちがおすすめ。

Discussion