MeWrite Docs

Laravel Route Model Binding Running Before Middleware

LaravelでRoute Model BindingがMiddlewareより先に実行される問題の原因と解決策

概要

LaravelでRoute Model Bindingを使用すると、Middlewareよりも先にモデルの解決(データベースクエリ)が実行されてしまう問題です。認証チェックやレート制限などの前にモデルが取得されるため、パフォーマンスやセキュリティの問題が発生します。

エラーメッセージ

ModelNotFoundException: No query results for model [App\Models\Post]
404 Not Found (before authentication check)
Unauthorized access to model before middleware runs

原因

  1. Route Model Bindingの実行順序: デフォルトではミドルウェアより先に解決される
  2. SubstituteBindingsミドルウェアの位置: ミドルウェアスタックでの優先順位の問題
  3. ルートパラメータの解決タイミング: コントローラーメソッド実行前に解決される

解決策

1. SubstituteBindingsミドルウェアの順序を変更

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// app/Http/Kernel.php
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        // SubstituteBindingsを最後に移動
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

// または認証ミドルウェアの後に配置
protected $middlewareGroups = [
    'web' => [
        // ... その他のミドルウェア
        \App\Http\Middleware\Authenticate::class,  // 先に認証
        \Illuminate\Routing\Middleware\SubstituteBindings::class,  // その後にバインディング
    ],
];

2. 明示的にバインディングを遅延させる(Laravel 11+)

1
2
3
4
5
6
7
8
// routes/web.php
use Illuminate\Support\Facades\Route;

Route::middleware(['auth'])->group(function () {
    Route::get('/posts/{post}', [PostController::class, 'show'])
        ->withoutMiddleware([\Illuminate\Routing\Middleware\SubstituteBindings::class])
        ->middleware(\Illuminate\Routing\Middleware\SubstituteBindings::class);
});

3. 手動でモデルを解決

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// バインディングを使わずに手動で解決
Route::get('/posts/{id}', [PostController::class, 'show'])
    ->middleware('auth');

// PostController.php
class PostController extends Controller
{
    public function show($id)
    {
        // 認証後にモデルを取得
        $post = Post::findOrFail($id);

        // 追加の認可チェック
        $this->authorize('view', $post);

        return view('posts.show', compact('post'));
    }
}

4. カスタムミドルウェアでバインディング

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// app/Http/Middleware/BindAfterAuth.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;

class BindAfterAuth
{
    public function handle(Request $request, Closure $next)
    {
        // ここで認証済みであることを確認できる
        $route = $request->route();

        // モデルバインディングを手動で実行
        foreach ($route->parameters() as $key => $value) {
            if (is_string($value) && $route->bindingFieldFor($key)) {
                $model = $route->bindingFieldFor($key);
                $route->setParameter($key, $model::findOrFail($value));
            }
        }

        return $next($request);
    }
}

5. ルートバインディングの条件付き解決

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// app/Providers/RouteServiceProvider.php
use Illuminate\Support\Facades\Route;

public function boot(): void
{
    // カスタムバインディングロジック
    Route::bind('post', function ($value) {
        // 認証済みの場合のみモデルを解決
        if (auth()->check()) {
            return Post::where('id', $value)
                ->where('user_id', auth()->id())
                ->firstOrFail();
        }

        // 未認証の場合は値をそのまま返す(後で処理)
        return $value;
    });
}

6. ポリシーでの制御

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/Policies/PostPolicy.php
class PostPolicy
{
    public function view(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}

// PostController.php
class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }

    public function show(Post $post)
    {
        // authorizeResourceにより自動的に認可チェック
        return view('posts.show', compact('post'));
    }
}

7. Implicit Bindingを無効化

1
2
3
4
5
6
// 特定のルートでImplicit Bindingを無効化
Route::get('/posts/{post}', [PostController::class, 'show'])
    ->withoutScopedBindings();

// または明示的なバインディングを使用
Route::get('/posts/{post:slug}', [PostController::class, 'show']);

8. ミドルウェアの優先順位を設定

1
2
3
4
5
6
7
8
// app/Http/Kernel.php
protected $middlewarePriority = [
    \Illuminate\Cookie\Middleware\EncryptCookies::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Auth\Middleware\Authenticate::class,  // 認証を先に
    \Illuminate\Routing\Middleware\SubstituteBindings::class,  // バインディングは後
];

デバッグ方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ミドルウェアの実行順序を確認
Route::get('/debug', function () {
    $route = request()->route();
    dd($route->gatherMiddleware());
});

// バインディングの解決タイミングを確認
Route::bind('post', function ($value) {
    \Log::info('Binding resolved', ['value' => $value, 'authenticated' => auth()->check()]);
    return Post::findOrFail($value);
});

よくある間違い

  • ミドルウェアの順序を意識せずにRoute Model Bindingを使用する
  • 認証前にモデルが存在するか確認してしまい、情報漏洩につながる
  • 404エラーと401/403エラーの順序が逆になり、リソースの存在が露呈する
  • パフォーマンスのために遅延バインディングを考慮しない

関連エラー

参考リンク

Laravel の他のエラー

最終更新: 2025-12-14