Laravel『Too Many Redirects(リダイレクトループ)』の原因と対処法

Laravel『Too Many Redirects(リダイレクトループ)』の原因と対処法

Laravelで「Too Many Redirects」が起きるときは、ブラウザやHTTPクライアントがリダイレクト(301/302/307/308)を繰り返して上限に達している状態で、Laravel側の例外というより「ルート・ミドルウェア・認証・HTTPS強制・プロキシ設定・サブドメイン/URL正規化」のどこかで相互に飛ばし合っているのが原因になりやすい。典型は「未ログイン→loginへ」「loginがさらに別条件で→元へ」「HTTPS強制が二重」「www有無で往復」「セッションCookieが維持できず常に未ログイン扱い」など。まずは“どのURLがどのURLへ何回飛んでいるか”を見える化し、ループしている条件を1つずつ潰す。

症状と発生条件(典型例)

発生時の見え方。
・ブラウザ:ERR_TOO_MANY_REDIRECTS / リダイレクトが繰り返されました
・curl:Locationヘッダが連鎖し続ける
発生条件の典型:
・authミドルウェアで未ログイン→loginへ飛ばすが、login側もさらにリダイレクトしている
・ゲスト専用(guest)と認証必須(auth)が同じルートに同居している
・HTTPSリダイレクトがアプリ側とWebサーバ側で二重化し、http⇔httpsを往復
・APP_URLやTrustProxiesが不正で、Laravelが常にhttp判定になりhttpsへ飛ばし続ける
・セッションCookieが保存/送信されず、ログインしても未ログイン扱いになりloginへ戻る

まず確認:リダイレクトの連鎖(Location)を追う

どこでループしているかを確定する。

# -I: HEADで確認, -L: リダイレクト追従, -v: 詳細
curl -I -L -v https://example.com

注目点:
・Location が http→https→http と往復していないか
・/login → / → /login のように同じ2地点を行ったり来たりしていないか
・Set-Cookie が毎回発行されているのに次リクエストでCookieが送られていないか

原因1:auth/guestミドルウェアの設定ミス(login周りの往復)

典型ループ:
・/dashboard は auth なので未ログイン→/login
・/login に guest を付けているが、判定が逆転して /dashboard へ戻す
・結果として /dashboard ⇔ /login を往復
ルート定義の例(意図がズレるとループ)。

// routes/web.php(例)
Route::middleware('auth')->get('/dashboard', fn() => view('dashboard'));

Route::middleware('guest')->get('/login', [AuthController::class, 'showLoginForm'])
  ->name('login');

対処の方向性:
・/login は guest(ログイン済みなら /dashboard へ)
・/dashboard は auth(未ログインなら /login へ)
・ログイン判定がセッションで成立していること(Cookieが維持できていること)が前提
ログイン済みユーザーをどこへ飛ばすかを明示するなら、guest側で一方向にする。

public function showLoginForm()
{
    if (auth()->check()) {
        return redirect()->route('dashboard');
    }
    return view('auth.login');
}

原因2:リダイレクト先ルート自身にも同じミドルウェアが付いている

例:/login に auth が付いていると、未ログイン→/loginへ行くが /login も auth なのでまた /login へ、というループが起きる。
チェックポイント:
・route:list で /login の middleware を確認

php artisan route:list

対処:
・ログイン画面、認証コールバック、パスワードリセット、ログアウト等は auth の対象外にする
・認証が必要な領域は prefix + middleware をまとめ、例外を明確にする

Route::middleware('auth')->prefix('admin')->group(function () {
    Route::get('/', ...);
});

Route::get('/login', ...)->middleware('guest')->name('login');

原因3:HTTPS強制が二重(http⇔httpsの往復)

よくある構成:
・Webサーバ(Nginx/ALB/Cloudflareなど)でHTTPSへリダイレクト
・Laravel側でも https 判定が狂っていて、逆に http へ戻す/または毎回 https 強制
結果として http と https を行ったり来たりして上限に達する。
対処:
・HTTPSリダイレクトは「どこでやるか」を1箇所に寄せる(Webサーバ側かアプリ側か)
・プロキシ配下なら TrustProxies を正しく設定し、Laravelが本来のスキーム(https)を認識できるようにする
プロキシ配下での典型設定(例)。

// app/Http/Middleware/TrustProxies.php
protected $proxies = '*';

protected $headers =
    \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
    \Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO;

さらに APP_URL が http のままだとURL生成がずれて誘発することがある。

# .env
APP_URL=https://example.com

原因4:セッションCookieが維持できず、常に未ログイン扱いになる

ログインPOSTは成功しているのに、次の画面で未ログイン扱い→loginへ戻る、を繰り返すとリダイレクトループになりやすい。
発生条件の典型:
・SESSION_DOMAIN が合っていない(www/サブドメイン差)
・HTTPSなのに SESSION_SECURE_COOKIE が false/true 不整合
・SameSite設定によりCookieが送られない
・クロスドメインSPAで credentials を付けていない
対処の方向性(代表例)。

# .env(例:サブドメインも含めたい)
SESSION_DOMAIN=.example.com
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax

クロスサイトで必要なら none + secure を検討(運用要件に依存)。

SESSION_SAME_SITE=none
SESSION_SECURE_COOKIE=true

Cookieが送れているかはブラウザのDevTools(Application → Cookies / Network)で確認すると早い。

原因5:www有無・末尾スラッシュ・言語プレフィックス等の正規化で往復している

URL正規化を複数箇所でやると「A→B→A」になりやすい。
例:
・Webサーバで www へ寄せる
・Laravel側で www なしへ寄せる
・/path と /path/ を相互にリダイレクト
・/ja と /en の自動リダイレクトが条件ズレ
対処:
・正規化のルールを1箇所に集約
・正規化は“片方向”だけにする(必ず同じ最終URLへ収束させる)
・ミドルウェアでやるなら、現在URLとリダイレクト先が同一になっていないかもチェックする

サンプル:ログイン前後のリダイレクトを安定させる(意図がブレない構成)

// AuthController(例:ループしないように一方向へ)
public function show()
{
    if (auth()->check()) {
        return redirect()->route('dashboard');
    }
    return view('auth.login');
}

public function login(\Illuminate\Http\Request $request)
{
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required'],
    ]);

    if (!auth()->attempt($credentials, true)) {
        return back()->withErrors(['email' => 'ログインに失敗しました']);
    }

    $request->session()->regenerate();

    return redirect()->intended(route('dashboard'));
}

チェックリスト(上から順に確認する)

1) curl -I -L -v で Location の連鎖を追い、どの2地点(または数地点)で往復しているか特定したか
2) /login と /dashboard など、リダイレクト先ルートの middleware(auth/guest)が矛盾していないか
3) /login に auth、/dashboard に guest など “逆付け” になっていないか
4) http⇔https の往復があるなら、Webサーバ側とLaravel側のHTTPS強制が二重になっていないか
5) プロキシ配下なら TrustProxies と APP_URL が正しく、Laravelが https を認識できているか
6) ログイン後に未ログイン扱いになるなら、セッションCookie(DOMAIN/SECURE/SameSite)が維持できているか
7) www有無・末尾スラッシュ・言語ルーティングなど正規化が複数箇所で相互に働いていないか