【第四弾】Laravel入門資料

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

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

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

ミドルウェアと権限管理

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

認証と認可の違い

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

認証(Authentication)

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

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

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

認可(Authorization)

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

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

// 特定のロールを持っているか
if (auth()->user()->hasRole('admin')) {
    echo '管理者権限があります';
}

具体例で理解しよう

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

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

Webアプリケーションの例

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

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

ミドルウェアの仕組み

ミドルウェアは、コントローラーが実行される前にチェックを行う「番人」のような仕組みです。

ミドルウェアファイルの構造

まず、ミドルウェアファイルがどう書かれているか見てみましょう。

// app/Http/Middleware/Authenticate.php (Laravel標準のミドルウェア)

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class Authenticate
{
    public function handle(Request $request, Closure $next)
    {
        // ログインしているかチェック
        if (!auth()->check()) {
            // ログインしていない → ログインページへリダイレクト
            return redirect('/login');
        }

        // ログインしている → 次の処理(コントローラー)へ進む
        return $next($request);
    }
}

📝 コードの構造

  • ミドルウェアは必ず handle() メソッドを持つ
  • • 第1引数 $request: ユーザーからのリクエスト情報
  • • 第2引数 $next: 次の処理(コントローラー)を実行する関数

$next($request) の意味

ミドルウェアの2つの選択肢:

ユーザーがアクセス
   ↓
[ミドルウェアの handle() メソッドが実行される]
   ↓
条件チェック
   ↓
✅ OK → return $next($request);     ← コントローラーへ進む
❌ NG → return redirect('/login');  ← ここで処理を止める

💡 重要ポイント

  • return $next($request);次の処理(コントローラー)へ進む
  • return redirect();処理を止めてリダイレクト
  • return abort(403);処理を止めてエラー表示

なぜ $next を使うの?

Laravelのミドルウェアは必ず $next($request) を使う設計です。
これは「次に何が実行されるか(別のミドルウェアかコントローラーか)をミドルウェア自身は知らない」ためです。
$next($request) で次に渡すのが標準的な書き方です。

具体例: 管理者チェックのミドルウェア

// app/Http/Middleware/IsAdmin.php

class IsAdmin
{
    public function handle(Request $request, Closure $next)
    {
        // 1. ログインしているかチェック
        if (!auth()->check()) {
            return redirect('/login');  // ← ここで止まる
        }

        // 2. 管理者かチェック
        if (!auth()->user()->isAdmin()) {
            return abort(403);  // ← ここで止まる
        }

        // 3. 両方OKならコントローラーへ
        return $next($request);  // ← コントローラー実行
    }
}

📝 補足: レスポンス後の処理も可能

ミドルウェアはレスポンスを返した後の処理も書けます(例: アクセスログの記録)。
ただし今回は使わないので、「リクエスト前のチェック」だけ覚えておけばOKです。

public function handle(Request $request, Closure $next)
{
    // リクエスト前の処理(今回使う)

    $response = $next($request);  // コントローラー実行

    // レスポンス後の処理(今回は使わない)
    // 例: Log::info('アクセスログ');

    return $response;
}

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->enum('role', ['admin', 'user'])->default('user')->after('name');
        });
    }

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

想定するロール:

  • 'user' - 一般ユーザー(デフォルト)
  • '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 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

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

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

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

✅ Tinkerで確認したこと:

  • ロールの変更と保存
  • isAdmin()での判定
  • 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 (Laravel 12)

use App\Http\Middleware\IsAdmin;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

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

💡 ポイント

  • • Laravel 11以降は bootstrap/app.phpwithMiddleware() でミドルウェアを登録します
  • $middleware->alias() で短い名前(例: 'admin')とクラスを紐づけます
  • • ルートで ->middleware('admin') と書くことで適用できます

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

認証セクションで作成した dashboard ルートを管理者専用に改修します。

// routes/web.php

// 管理者専用ページ (adminミドルウェアでグループ化)
Route::middleware(['admin'])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
});

ポイント:

  • middleware(['admin'])->group() でグループ化すると、複数のルートにまとめて適用できる
  • 認証セクションで作成したdashboardルートを管理者専用に変更
  • adminミドルウェア内でログインチェックもしているので、'auth' は不要

💡 複数のミドルウェアも設定可能

今回は['admin']だけですが、複数のミドルウェアも指定できます:

// 複数のミドルウェアを適用する例
Route::middleware(['auth', 'verified', 'admin'])->group(function () {
    // このグループ内のルートは3つのミドルウェアを通過する
});

配列の順番通りに実行されます。今回は必要ないので覚えておくだけでOKです。

実践: 実際にアクセスしてテストしてみよう

ステップ1: Tinkerでユーザーを作成

php artisan tinker
// 管理者を作成
User::create([
    'name' => '管理者',
    'email' => 'admin@example.com',
    'password' => bcrypt('password'),
    'role' => 'admin',
]);

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

ステップ2: ブラウザでアクセスして確認

1️⃣ 管理者でログイン

  • http://localhost/login にアクセス
  • • Email: admin@example.com
  • • Password: password

2️⃣ ダッシュボードにアクセス

  • • ログイン後、自動的に http://localhost/dashboard へリダイレクト
  • ✅ 管理者なのでアクセスできる!

3️⃣ ログアウトして一般ユーザーでログイン

  • • Email: user@example.com
  • • ログイン後、http://localhost/dashboard へ自動リダイレクト
  • ❌ 403エラー表示! (一般ユーザーはアクセスできない)

🎓 何が起きたか?

  • ✅ 管理者: isAdmin()true → ミドルウェア通過 → ページ表示
  • ❌ 一般ユーザー: isAdmin()false → ミドルウェアで abort(403) → エラー画面

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

一般ユーザー用の画面を作成

管理者以外の一般ユーザーもログイン後にアクセスできる画面を作成しましょう。

ステップ1: 一般ユーザー用ビューの作成

<!-- resources/views/home.blade.php -->
<h1>ホーム画面</h1>
<p>ようこそ、{{ auth()->user()->name }}さん</p>
<p>あなたは一般ユーザーです。</p>

ステップ2: ルートの追加

// routes/web.php

// 管理者専用ページ
Route::middleware(['admin'])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
});

// 一般ユーザー用ページ (認証のみ必要)
Route::middleware(['auth'])->group(function () {
    Route::get('/home', function () {
        return view('home');
    })->name('home');
});

ポイント:

  • /dashboard - 管理者専用 (adminミドルウェア)
  • /home - 一般ユーザー用 (authミドルウェアのみ)
  • 役割によって画面を分けることで、権限管理が明確になる

ステップ3: ログイン後の振り分け

ログイン後に役割に応じて適切な画面へリダイレクトするように、LoginControllerを修正します。

// app/Http/Controllers/Auth/LoginController.php

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

    if (Auth::attempt($credentials)) {
        $request->session()->regenerate();

        // 管理者なら /dashboard へ、一般ユーザーなら /home へ
        if (auth()->user()->isAdmin()) {
            return redirect()->route('dashboard');
        }

        return redirect()->route('home');
    }

    return back()->withErrors([
        'email' => 'メールアドレスまたはパスワードが正しくありません。',
    ])->onlyInput('email');
}

💡 何が起きるか?

  • ✅ 管理者: ログイン後 → /dashboard へ自動リダイレクト
  • ✅ 一般ユーザー: ログイン後 → /home へ自動リダイレクト
  • ✅ 一般ユーザーが /dashboard に直接アクセス → 403エラー

実践: 動作確認

1️⃣ 管理者でログイン

  • • Email: admin@example.com
  • • ログイン後 → /dashboard へ自動リダイレクト

2️⃣ 一般ユーザーでログイン

  • • Email: user@example.com
  • • ログイン後 → /home へ自動リダイレクト

3️⃣ 一般ユーザーが /dashboard にアクセスしようとすると

  • • URLに直接 http://localhost/dashboard を入力
  • 403エラー! 管理者専用のため

✅ 役割に応じた画面の振り分けが実装できました!

まとめ

この章で学んだこと

  • 認証と認可の違い - 「あなたは誰?」と「あなたには権限がある?」
  • ミドルウェアの仕組み - コントローラー実行前のチェック、$next($request)で進む
  • ミドルウェアファイルの構造 - handle()メソッド、$request$next
  • enum型でロール管理 - enum('role', ['admin', 'user'])で型安全に
  • カスタムミドルウェア作成 - php artisan make:middleware IsAdmin
  • ミドルウェア登録 - bootstrap/app.phpwithMiddleware()alias()登録
  • ルートへの適用 - Route::middleware(['admin'])->group()でグループ化
  • 役割別画面の振り分け - 管理者は/dashboard、一般ユーザーは/home
  • ログイン後のリダイレクト - LoginControllerisAdmin()判定して振り分け

重要なポイント

1. ミドルウェアは「番人」

条件を満たせばreturn $next($request)で次へ、満たさなければreturn redirect()abort()で止める

2. adminミドルウェアでログインチェックも行う

middleware(['admin'])だけで、ログインチェック+管理者チェックの両方を実行

3. group()でまとめて適用

複数のルートに同じミドルウェアを適用する場合はRoute::middleware()->group()を使う

実践で身につけたスキル

  • usersテーブルenum型のroleカラムを追加
  • UserモデルにisAdmin()メソッドを追加
  • IsAdminミドルウェアでログイン・管理者チェックを実装
  • bootstrap/app.php'admin' => IsAdmin::classと登録
  • routes/web.phpで管理者用・一般ユーザー用ルートを分離
  • LoginControllerで役割に応じたリダイレクト先を振り分け
  • ブラウザで403エラーを確認して権限管理の動作を体験

🎉 ミドルウェアによる権限管理が完璧に理解できました!