【第四弾】Laravel入門資料

📚 Laravel公式ドキュメント - Middleware / Authorization

https://laravel.com/docs/12.x/middleware https://laravel.com/docs/12.x/authorization

💡 この講座では、上記の公式ドキュメントを基に解説していきます。
公式ドキュメントは初学者には内容が難しいため、エッセンスを優しく噛み砕いて解説していきます。

ミドルウェアと権限管理

認証機能だけでは不十分です。誰が何をできるかを管理する権限管理(認可)が必要です。
この章では、ミドルウェアとロールを使った権限管理を学びます。

🎯 この章で学ぶこと

  • ✅ 認証と認可の違い
  • ✅ ミドルウェアの仕組みと使い方
  • ✅ ロールテーブルの設計(シンプル版と多対多版)
  • ✅ カスタムミドルウェアの作成
  • ✅ Tinkerでのロール割り当てと権限チェック

認証と認可の違い

前章で学んだのは認証(Authentication)です。
この章で学ぶのは認可(Authorization)です。

認証(Authentication)

「あなたは誰ですか?」を確認すること。
ログイン機能がこれにあたります。

// 認証:ログインしているか確認
if (auth()->check()) {
    echo 'ログインしています';
}

// 誰がログインしているか
echo auth()->user()->name;  // 「田中太郎」

認可(Authorization)

「あなたには権限がありますか?」を確認すること。
管理者のみアクセス可能なページを作る機能がこれにあたります。

// 認可:管理者権限を持っているか確認
if (auth()->user()->isAdmin()) {
    echo '管理者です';
}

// 編集者権限を持っているか
if (auth()->user()->hasRole('editor')) {
    echo '記事を編集できます';
}

具体例で理解しよう

会社のオフィスに例えると:

  • 認証: 社員証でゲートを通る
    → 「あなたは社員である」ことを確認
  • 認可: 役職によって入れる部屋が異なる
    → 「一般社員は会議室Aにアクセスできるが、役員会議室には入れない」

Webアプリケーションの例

ページ 認証 認可
トップページ 不要 不要
ダッシュボード 必要(ログイン必須) 不要
記事編集 必要(ログイン必須) 必要(編集者または管理者)
ユーザー管理 必要(ログイン必須) 必要(管理者のみ)

✅ 認証と認可の違いが理解できました!

ミドルウェアの仕組み

ミドルウェアは、リクエストが処理される前レスポンスが返される後に、
特定の処理を挟み込む仕組みです。

ミドルウェアの流れ:

1. ユーザーがリクエスト送信
   ↓
2. ミドルウェア1(例: CSRF保護)
   ↓
3. ミドルウェア2(例: 認証チェック)
   ↓
4. ミドルウェア3(例: 権限チェック)
   ↓
5. コントローラー処理
   ↓
6. レスポンス返却

Laravelの標準ミドルウェア

ミドルウェア 役割
auth ログインしているかチェック(認証)
guest ログインしていないかチェック
verified メール認証済みかチェック
throttle レート制限(アクセス回数制限)

authミドルウェアの使い方

// routes/web.php

// 方法1: 個別のルートに適用
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

// 方法2: 複数のルートにまとめて適用
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::get('/settings', [SettingsController::class, 'index']);
});

// 方法3: コントローラーで適用
class DashboardController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index()
    {
        return view('dashboard');
    }
}

authミドルウェアの動き:

  • ログインしている → コントローラー処理を続行
  • ログインしていない → /login にリダイレクト

✅ ミドルウェアの仕組みが理解できました!

ロールテーブルの設計(シンプル版)

まずは、usersテーブルにroleカラムを追加するシンプルな方法を学びます。

ステップ1: マイグレーション作成

# マイグレーション作成
php artisan make:migration add_role_to_users_table
// database/migrations/xxxx_add_role_to_users_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('role')->default('user');
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
};
# マイグレーション実行
php artisan migrate

想定するロール:

  • 'user' - 一般ユーザー(デフォルト)
  • 'editor' - 編集者(記事の作成・編集が可能)
  • 'admin' - 管理者(全ての操作が可能)

ステップ2: Userモデルにメソッド追加

// app/Models/User.php

class User extends Authenticatable
{
    protected $fillable = [
        'name',
        'email',
        'password',
        'role',  // ← 追加
    ];

    /**
     * 管理者かどうか
     */
    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

    /**
     * 編集者かどうか
     */
    public function isEditor(): bool
    {
        return $this->role === 'editor';
    }

    /**
     * 特定のロールを持っているかチェック
     */
    public function hasRole(string $role): bool
    {
        return $this->role === $role;
    }

    /**
     * いずれかのロールを持っているかチェック
     */
    public function hasAnyRole(array $roles): bool
    {
        return in_array($this->role, $roles);
    }
}

ステップ3: Tinkerでロールを設定してみよう

# Tinkerを起動
php artisan tinker
// ユーザーを取得
$user = User::find(1);

// 現在のロールを確認
$user->role;
// => "user"(デフォルト)

// 管理者に昇格
$user->role = 'admin';
$user->save();

// 管理者かどうか確認
$user->isAdmin();
// => true

$user->hasRole('admin');
// => true

// 編集者ではない
$user->isEditor();
// => false

// 別のユーザーを編集者にする
$editor = User::find(2);
$editor->update(['role' => 'editor']);

// 編集者または管理者かチェック
$editor->hasAnyRole(['editor', 'admin']);
// => true(編集者なので)

// 一般ユーザーを作成
$normalUser = User::create([
    'name' => '一般ユーザー',
    'email' => 'user@example.com',
    'password' => 'password',
    'role' => 'user',  // デフォルトは'user'なので省略可
]);

$normalUser->isAdmin();
// => false

$normalUser->hasRole('user');
// => true

✅ Tinkerで確認したこと:

  • ロールの変更と保存
  • isAdmin()、isEditor()での判定
  • hasRole()での特定ロールチェック
  • hasAnyRole()での複数ロールチェック

✅ シンプルなロール管理が実装できました!

カスタムミドルウェアの作成

管理者のみアクセス可能なページを作るため、カスタムミドルウェアを作成します。

ステップ1: ミドルウェア作成

# ミドルウェア作成
php artisan make:middleware IsAdmin
// app/Http/Middleware/IsAdmin.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class IsAdmin
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        // ログインしているか確認
        if (!auth()->check()) {
            return redirect('/login');
        }

        // 管理者かどうか確認
        if (!auth()->user()->isAdmin()) {
            abort(403, 'このページにアクセスする権限がありません。');
        }

        // 管理者なら次の処理へ
        return $next($request);
    }
}

ミドルウェアの動き:

  1. ログインしていない → /login にリダイレクト
  2. ログインしているが管理者ではない → 403エラー(Forbidden)
  3. 管理者 → 次の処理(コントローラー)へ

ステップ2: ミドルウェアを登録

// bootstrap/app.php

use App\Http\Middleware\IsAdmin;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // ミドルウェアのエイリアスを登録
        $middleware->alias([
            'admin' => IsAdmin::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

ステップ3: ルートに適用

// routes/web.php

// 管理者専用ページ
Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'dashboard']);
    Route::get('/admin/users', [AdminController::class, 'users']);
    Route::post('/admin/users/{id}/role', [AdminController::class, 'updateRole']);
});

// または個別に適用
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])
    ->middleware(['auth', 'admin']);

ミドルウェアの順序:

  • ['auth', 'admin'] - まず認証チェック、次に管理者チェック
  • 順序が重要:認証されていないのに管理者チェックするとエラー

実践: Tinkerでテストしてみよう

# Tinkerを起動
php artisan tinker
// 管理者ユーザーを作成
$admin = User::create([
    'name' => '管理者',
    'email' => 'admin@example.com',
    'password' => 'password',
    'role' => 'admin',
]);

// ログインシミュレーション
Auth::login($admin);

// 現在のユーザーが管理者か確認
auth()->user()->isAdmin();
// => true

// 一般ユーザーを作成
$user = User::create([
    'name' => '一般ユーザー',
    'email' => 'user@example.com',
    'password' => 'password',
    'role' => 'user',
]);

// 一般ユーザーでログイン
Auth::login($user);

auth()->user()->isAdmin();
// => false

// この状態で /admin/dashboard にアクセスすると403エラー

編集者用ミドルウェアも作ってみよう

php artisan make:middleware IsEditorOrAdmin
// app/Http/Middleware/IsEditorOrAdmin.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class IsEditorOrAdmin
{
    public function handle(Request $request, Closure $next): Response
    {
        if (!auth()->check()) {
            return redirect('/login');
        }

        // 編集者または管理者かチェック
        if (!auth()->user()->hasAnyRole(['editor', 'admin'])) {
            abort(403, 'このページにアクセスする権限がありません。');
        }

        return $next($request);
    }
}
// bootstrap/app.php

$middleware->alias([
    'admin' => IsAdmin::class,
    'editor' => IsEditorOrAdmin::class,  // ← 追加
]);
// routes/web.php

// 編集者または管理者のみアクセス可能
Route::middleware(['auth', 'editor'])->group(function () {
    Route::get('/posts/create', [PostController::class, 'create']);
    Route::post('/posts', [PostController::class, 'store']);
    Route::get('/posts/{id}/edit', [PostController::class, 'edit']);
    Route::put('/posts/{id}', [PostController::class, 'update']);
});

✅ カスタムミドルウェアで権限管理ができました!

ロールテーブルの設計(多対多版)

シンプル版では1ユーザー1ロールでしたが、1ユーザーが複数ロールを持てる多対多の設計を学びます。

多対多が必要なケース:

  • 「編集者」かつ「モデレーター」のような複数の役割
  • プロジェクトごとに異なる権限を持つ
  • より柔軟な権限管理が必要

テーブル設計

users テーブル
├─ id
├─ name
├─ email
└─ password

roles テーブル(ロール定義)
├─ id
├─ name (admin, editor, moderator など)
└─ description

role_user テーブル(中間テーブル)
├─ id
├─ user_id  (外部キー → users.id)
└─ role_id  (外部キー → roles.id)

マイグレーション作成

# rolesテーブル作成
php artisan make:migration create_roles_table

# 中間テーブル作成
php artisan make:migration create_role_user_table
// database/migrations/xxxx_create_roles_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->string('description')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('roles');
    }
};
// database/migrations/xxxx_create_role_user_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('role_user', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignId('role_id')->constrained()->onDelete('cascade');
            $table->timestamps();

            // 同じユーザーに同じロールを重複して割り当てないようにする
            $table->unique(['user_id', 'role_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('role_user');
    }
};
# マイグレーション実行
php artisan migrate

Roleモデル作成

php artisan make:model Role
// app/Models/Role.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    protected $fillable = ['name', 'description'];

    /**
     * このロールを持つユーザー
     */
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

Userモデルにリレーション追加

// app/Models/User.php

class User extends Authenticatable
{
    // ... 既存のコード ...

    /**
     * ユーザーが持つロール
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    /**
     * 特定のロールを持っているかチェック
     */
    public function hasRole(string $roleName): bool
    {
        return $this->roles()->where('name', $roleName)->exists();
    }

    /**
     * いずれかのロールを持っているかチェック
     */
    public function hasAnyRole(array $roleNames): bool
    {
        return $this->roles()->whereIn('name', $roleNames)->exists();
    }

    /**
     * すべてのロールを持っているかチェック
     */
    public function hasAllRoles(array $roleNames): bool
    {
        return $this->roles()->whereIn('name', $roleNames)->count() === count($roleNames);
    }

    /**
     * ロールを割り当て
     */
    public function assignRole(string $roleName): void
    {
        $role = Role::where('name', $roleName)->firstOrFail();
        $this->roles()->syncWithoutDetaching($role);
    }

    /**
     * ロールを削除
     */
    public function removeRole(string $roleName): void
    {
        $role = Role::where('name', $roleName)->first();
        if ($role) {
            $this->roles()->detach($role);
        }
    }
}

Tinkerで多対多ロールを試そう

# Tinkerを起動
php artisan tinker
// ロールを作成
$adminRole = Role::create([
    'name' => 'admin',
    'description' => '管理者',
]);

$editorRole = Role::create([
    'name' => 'editor',
    'description' => '編集者',
]);

$moderatorRole = Role::create([
    'name' => 'moderator',
    'description' => 'モデレーター',
]);

// 作成されたロールを確認
Role::all();

// ユーザーを取得
$user = User::find(1);

// ロールを割り当て
$user->roles()->attach($adminRole);
$user->roles()->attach($editorRole);

// または
$user->assignRole('admin');
$user->assignRole('editor');

// ユーザーが持つロールを確認
$user->roles;
// => Collection {
//      Role {id: 1, name: "admin", ...},
//      Role {id: 2, name: "editor", ...}
//    }

// ロール名だけ取得
$user->roles->pluck('name');
// => ["admin", "editor"]

// 特定のロールを持っているか確認
$user->hasRole('admin');
// => true

$user->hasRole('moderator');
// => false

// いずれかのロールを持っているか
$user->hasAnyRole(['editor', 'moderator']);
// => true(editorを持っている)

// すべてのロールを持っているか
$user->hasAllRoles(['admin', 'editor']);
// => true

$user->hasAllRoles(['admin', 'editor', 'moderator']);
// => false(moderatorを持っていない)

// ロールを削除
$user->removeRole('editor');

$user->roles->pluck('name');
// => ["admin"]

// 別のユーザーに複数ロールを一度に割り当て
$user2 = User::find(2);
$user2->roles()->attach([$editorRole->id, $moderatorRole->id]);

$user2->roles->pluck('name');
// => ["editor", "moderator"]

// 特定のロールを持つユーザーを検索
$editors = User::whereHas('roles', function ($query) {
    $query->where('name', 'editor');
})->get();

// 管理者ロールを持つユーザー数をカウント
User::whereHas('roles', function ($query) {
    $query->where('name', 'admin');
})->count();
// => 1

✅ Tinkerで確認したこと:

  • Roleモデルでロール作成
  • attach()でロール割り当て
  • assignRole()とremoveRole()の使い方
  • hasRole()、hasAnyRole()、hasAllRoles()での判定
  • whereHas()で特定ロールを持つユーザー検索

✅ 多対多のロール管理が実装できました!

ミドルウェアの深いメカニズム(オニオンアーキテクチャ)

ミドルウェアがどのように動作するのか、オニオンアーキテクチャ(玉ねぎ構造)を理解しましょう。

オニオンアーキテクチャとは

Laravelのミドルウェアは、玉ねぎの層のようにリクエストを包んで処理します。
リクエストは外側の層から内側へ、レスポンスは内側から外側へと通過します。

公式ドキュメントの図:

Laravelの公式ドキュメントには、ミドルウェアのオニオン構造を示す図があります:
Laravel Middleware Documentation - Middleware and Responses

オニオンモデルのイメージ:

         リクエスト →
              ↓
    ┌─────────────────────────┐
    │  Middleware 1 (外側)    │ ← CSRF保護
    │  ┌───────────────────┐  │
    │  │ Middleware 2      │  │ ← セッション管理
    │  │  ┌─────────────┐  │  │
    │  │  │ Middleware 3│  │  │ ← 認証チェック
    │  │  │  ┌───────┐  │  │  │
    │  │  │  │ Core  │  │  │  │ ← コントローラー
    │  │  │  │(中心)│  │  │  │
    │  │  │  └───────┘  │  │  │
    │  │  └─────────────┘  │  │
    │  └───────────────────┘  │
    └─────────────────────────┘
              ↓
         レスポンス ←

リクエストとレスポンスの流れ

リクエストの流れ(外→内):

1. ユーザーがリクエスト送信
   ↓
2. Middleware 1 (CSRF保護)
   - トークンをチェック
   - OK → 次へ、NG → 403エラー
   ↓
3. Middleware 2 (セッション管理)
   - セッションを開始
   - セッションデータを読み込む
   ↓
4. Middleware 3 (認証チェック)
   - ログインしているかチェック
   - OK → 次へ、NG → /login にリダイレクト
   ↓
5. コントローラー実行
   - ビジネスロジック処理
   - レスポンスを生成

レスポンスの流れ(内→外):

5. コントローラーがレスポンスを返す
   ↓
4. Middleware 3 (認証チェック)
   - レスポンスに何か追加できる(通常はそのまま通す)
   ↓
3. Middleware 2 (セッション管理)
   - セッションを保存
   - Set-Cookie ヘッダーを追加
   ↓
2. Middleware 1 (CSRF保護)
   - レスポンスに何か追加できる(通常はそのまま通す)
   ↓
1. ユーザーにレスポンス返却

ミドルウェアの2つのタイプ

1. Before Middleware(リクエストの前処理)

リクエストがコントローラーに到達する前に処理を行います。

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class BeforeMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // ここで前処理を実行
        // 例: ログイン状態をチェック
        if (!auth()->check()) {
            return redirect('/login');
        }

        // 次のミドルウェア or コントローラーへ
        return $next($request);
    }
}
2. After Middleware(レスポンスの後処理)

コントローラーがレスポンスを生成した後に処理を行います。

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class AfterMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // まず次のミドルウェア or コントローラーを実行
        $response = $next($request);

        // レスポンスが生成された後の処理
        // 例: ヘッダーを追加
        $response->header('X-Custom-Header', 'MyValue');

        // ログを記録
        \Log::info('Response generated', [
            'url' => $request->url(),
            'status' => $response->status(),
        ]);

        return $response;
    }
}

実例: ミドルウェアの適用順序を確認

// routes/web.php

Route::get('/test', function () {
    \Log::info('コントローラー実行');
    return 'Hello World';
})->middleware(['first', 'second', 'third']);
// app/Http/Middleware/FirstMiddleware.php
class FirstMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        \Log::info('FirstMiddleware: リクエスト前処理');

        $response = $next($request);

        \Log::info('FirstMiddleware: レスポンス後処理');
        return $response;
    }
}

// app/Http/Middleware/SecondMiddleware.php
class SecondMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        \Log::info('SecondMiddleware: リクエスト前処理');

        $response = $next($request);

        \Log::info('SecondMiddleware: レスポンス後処理');
        return $response;
    }
}

// app/Http/Middleware/ThirdMiddleware.php
class ThirdMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        \Log::info('ThirdMiddleware: リクエスト前処理');

        $response = $next($request);

        \Log::info('ThirdMiddleware: レスポンス後処理');
        return $response;
    }
}

ログ出力結果:

FirstMiddleware: リクエスト前処理   ← 外側から
SecondMiddleware: リクエスト前処理  ←
ThirdMiddleware: リクエスト前処理   ←
コントローラー実行                   ← 中心
ThirdMiddleware: レスポンス後処理   ← 内側から
SecondMiddleware: レスポンス後処理  ←
FirstMiddleware: レスポンス後処理   ← 外側へ

重要なポイント:

  • リクエストの流れ: 外側から内側へ(First → Second → Third → Controller)
  • レスポンスの流れ: 内側から外側へ(Controller → Third → Second → First)
  • $next($request) を呼ぶことで次の層へ渡す
  • return $next($request) の前がBefore処理、後がAfter処理

ミドルウェアで処理を中断する

$next($request) を呼ばずに直接レスポンスを返すと、
それ以降のミドルウェアやコントローラーは実行されません。

class CheckAge
{
    public function handle(Request $request, Closure $next)
    {
        if ($request->age < 18) {
            // ここで処理を中断!
            // コントローラーは実行されない
            return redirect('/underage');
        }

        // 18歳以上なら次へ進む
        return $next($request);
    }
}

処理が中断された場合のフロー:

1. ユーザーがリクエスト送信(age=15)
   ↓
2. Middleware 1
   ↓
3. Middleware 2
   ↓
4. CheckAge ミドルウェア
   - age < 18 を検出
   - return redirect('/underage');  ← ここで中断!
   ↓
(Middleware 3 や コントローラーは実行されない)
   ↓
4. CheckAge ミドルウェア(レスポンス返却)
   ↓
3. Middleware 2(レスポンス返却)
   ↓
2. Middleware 1(レスポンス返却)
   ↓
1. ユーザーに /underage へのリダイレクトレスポンスが返る

グローバルミドルウェア vs ルートミドルウェア

種類 適用範囲 設定場所
グローバル 全てのリクエスト bootstrap/app.php の withMiddleware()
ミドルウェアグループ web or api グループ bootstrap/app.php
ルートミドルウェア 特定のルートのみ routes/web.php で ->middleware()
// bootstrap/app.php

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // グローバルミドルウェア(全てのリクエストに適用)
        $middleware->append(MyGlobalMiddleware::class);

        // webグループにミドルウェア追加
        $middleware->web(append: [
            MyWebMiddleware::class,
        ]);

        // ミドルウェアエイリアス登録
        $middleware->alias([
            'admin' => IsAdmin::class,
            'verified' => EnsureEmailIsVerified::class,
        ]);
    })
    ->create();

Tinkerでミドルウェアの挙動を理解する

実際にミドルウェアを作って、動作を確認してみましょう。

# ミドルウェア作成
php artisan make:middleware LogMiddleware
// app/Http/Middleware/LogMiddleware.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class LogMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // リクエスト前処理
        \Log::info('リクエスト受信', [
            'url' => $request->url(),
            'method' => $request->method(),
            'ip' => $request->ip(),
        ]);

        // 次の処理へ
        $response = $next($request);

        // レスポンス後処理
        \Log::info('レスポンス送信', [
            'status' => $response->status(),
        ]);

        return $response;
    }
}
// bootstrap/app.php

$middleware->alias([
    'log' => \App\Http\Middleware\LogMiddleware::class,
]);
// routes/web.php

Route::get('/test', function () {
    return 'Hello World';
})->middleware('log');

ブラウザで /test にアクセスすると、storage/logs/laravel.log にログが記録されます。

✅ ミドルウェアのオニオンアーキテクチャが理解できました!

まとめ

この章で学んだこと

1. 認証と認可の違い

  • 認証: 「あなたは誰?」を確認(ログイン機能)
  • 認可: 「あなたには権限がある?」を確認(ロール・権限管理)

2. ミドルウェアの仕組み

  • リクエストとレスポンスの間に処理を挟み込む
  • オニオンアーキテクチャ(玉ねぎ構造)で多層処理
  • Before Middleware(前処理)とAfter Middleware(後処理)
  • $next($request)を呼ぶことで次の層へ
  • 処理を中断してリダイレクトやエラーを返せる

3. ロール管理(シンプル版)

  • usersテーブルにroleカラムを追加
  • isAdmin()、hasRole()などのメソッド実装
  • Tinkerでロール割り当てと確認

4. カスタムミドルウェア

  • php artisan make:middleware で作成
  • bootstrap/app.phpでエイリアス登録
  • ルートに->middleware()で適用
  • IsAdmin、IsEditorOrAdminなど用途別に作成

5. ロール管理(多対多版)

  • roles、role_userテーブルで柔軟な権限管理
  • 1ユーザーが複数ロールを持てる
  • belongsToManyでリレーション定義
  • attach()、detach()でロール割り当て・削除
  • hasRole()、hasAnyRole()、hasAllRoles()で判定
  • whereHas()で特定ロールを持つユーザー検索

6. オニオンアーキテクチャ

  • ミドルウェアは玉ねぎの層構造
  • リクエストは外→内、レスポンスは内→外
  • middleware(['first', 'second'])の順序で適用
  • グローバル、ミドルウェアグループ、ルートミドルウェア

シンプル版 vs 多対多版の使い分け

シンプル版 多対多版
構造 usersテーブルにroleカラム roles + role_user テーブル
ロール数 1ユーザー1ロール 1ユーザー複数ロール
実装難易度 簡単 やや複雑
柔軟性 低い 高い
適している場合 小規模アプリ、シンプルな権限 大規模アプリ、複雑な権限管理

次のステップ

この章では、ミドルウェアとロールによる権限管理を学びました。
さらに学びたい方は以下のトピックに進んでください:

  • Gate - クロージャベースの認可ロジック
  • Policy - モデル単位の認可ロジック(誰がどの記事を編集できるか等)
  • Permission(パーミッション) - より細かい権限管理(create, read, update, delete)
  • Laravel Permission パッケージ - Spatie社の人気パッケージ

🎉 ミドルウェアとロール管理の章が完了しました!

認証と認可の違い、オニオンアーキテクチャ、ロールテーブルの設計、カスタムミドルウェアの作成、
そしてTinkerでの実践的な使い方を学びました。
これで実務で使える権限管理の基礎が身につきました!