【Laravel】ロール・権限管理の実装パターン

laravelアイキャッチ Laravel

こんにちは、かつコーチです。

前回はパスワードハッシュ化(bcrypt)の仕組みを解説しました。

ログイン機能ができると、次にぶつかるのが「管理者だけがこの画面を見られるようにしたい」という要望です。

今回はLaravelにおけるロール・権限管理の実装パターンを、認証・セキュリティ編の締めくくりとして解説します。

ロール・権限管理とは何か

認証(Authentication)と認可(Authorization)の違い

まず整理しておきたいのが、認証認可という2つの用語です。

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

認可は「あなたに、この操作をする権限がありますか」を確認する処理です。

ログインできることと、管理画面を操作できることは、本来別の話です。

この違いを意識せずに実装を始めると、後で権限チェックがあちこちに散らばる原因になります。

なぜアプリ規模が大きくなると必要になるのか

小規模なアプリでは「ログインしている=全機能が使える」でも困りません。

しかし、一般ユーザー・編集者・管理者のように役割が増えると、ユーザーごとにできることを制限する仕組みが必要になります。

私が初めて権限管理を実装したときは、この「役割ごとの線引き」を後から追加したため、既存のコントローラ全部に修正が入る羽目になりました。

権限管理は、アプリ設計の初期段階で方針を決めておくと後々楽になります。

実装パターンの選択肢

Laravelでロール・権限管理を実装する方法はいくつかあります。

3つの代表的な方法

方法特徴向いている規模
enumカラムでシンプルに管理usersテーブルにroleカラムを持たせるだけ役割が2〜3種類の小規模アプリ
Laravel標準のGate・Policyフレームワーク標準機能、追加パッケージ不要権限ロジックが複雑な中規模アプリ
spatie/laravel-permissionロール・権限をDBで柔軟に管理できるパッケージ役割・権限が動的に増減する中〜大規模アプリ

判断軸

迷ったときは、次の軸で選ぶのがおすすめです。

  • 役割が「管理者かどうか」程度で固定なら、enumカラム+Gateで十分
  • 「編集者は記事の編集はできるが削除はできない」のように権限が細かく分かれるなら、Policyを使う
  • 管理画面からロールや権限を追加・変更できるようにしたいなら、spatie/laravel-permissionを使う

いきなりパッケージ導入から入るのではなく、まずはLaravel標準機能で仕組みを理解してから、必要に応じてパッケージに移行するのが個人的にはおすすめです。

Gate・Policyを使った基本実装

Policyの作成と登録

記事の編集・削除権限を例に、Policyを作ってみます。

php artisan make:policy PostPolicy --model=Post
<?php
// app/Policies/PostPolicy.php
namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->role === 'admin';
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->role === 'admin';
    }
}

updateは投稿者本人か管理者、deleteは管理者のみ許可、という判定ロジックです。

コントローラでの利用

Policyを定義したら、コントローラではauthorizeメソッドで呼び出します。

<?php
// app/Http/Controllers/PostController.php
public function destroy(Post $post)
{
    $this->authorize('delete', $post);

    $post->delete();

    return redirect()->route('posts.index');
}

権限がない場合は自動的に403エラーが返るため、コントローラ側でif文を毎回書く必要がありません。

Bladeでの利用

ビュー側で「削除ボタンを管理者にだけ表示する」といった制御は@canディレクティブを使います。

@can('delete', $post)
    <button type="submit">削除する</button>
@endcan

spatie/laravel-permissionでロール・権限管理

役割や権限をDB側で柔軟に増減させたい場合は、spatie/laravel-permissionが定番です。

インストールとセットアップ

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate

マイグレーションを実行すると、ロール・権限を管理するテーブルが作成されます。

ロール・権限の付与

<?php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

$role = Role::create(['name' => 'editor']);
Permission::create(['name' => 'edit posts']);

$role->givePermissionTo('edit posts');
$user->assignRole('editor');

管理画面から「新しいロールを追加する」といった動的な運用がしたい場合、このパッケージが向いています。

ミドルウェアでのルート保護

<?php
// routes/web.php
Route::middleware(['role:admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'index']);
});

roleミドルウェアを使うことで、ルート単位で権限チェックをかけられます。

つまずきやすいポイント:権限チェックをコントローラに直書きする問題

分岐が増えて保守しづらくなる典型例

権限管理を導入したばかりの頃、私はPolicyを使わず、コントローラの中に条件分岐を直書きしていました。

❌ Before:コントローラに条件分岐を直書きする

<?php
public function destroy(Post $post)
{
    if (auth()->user()->role !== 'admin' && auth()->user()->id !== $post->user_id) {
        abort(403);
    }

    $post->delete();

    return redirect()->route('posts.index');
}

このやり方は最初は動きますが、「編集」「公開」「コメント削除」と権限判定が増えるたびに、同じような条件分岐が別のコントローラにもコピーされていきます。

私のプロジェクトでも、途中で権限ルールを1つ変えるために、5つ以上のコントローラを修正して回るはめになりました。

✅ After:Policyに判定ロジックを集約する

<?php
public function destroy(Post $post)
{
    $this->authorize('delete', $post);

    $post->delete();

    return redirect()->route('posts.index');
}

権限判定のロジックをPostPolicyという1箇所にまとめておけば、ルールが変わってもPolicyクラスを直すだけで済みます。

条件分岐がコントローラに散らばり始めたら、Policyへの切り出しを検討するサインだと考えてください。

まとめ

この記事のポイント

  • 認証(誰か)と認可(何ができるか)は別の概念として扱う
  • ロール・権限管理は、enumカラム・Gate/Policy・spatie/laravel-permissionの3パターンから規模に応じて選ぶ
  • Policyを使うと権限判定ロジックを1箇所に集約でき、コントローラの条件分岐が増えるのを防げる
  • spatie/laravel-permissionは、ロール・権限を動的に管理したい中〜大規模アプリに向いている

次に読むべき記事

認証・セキュリティ編はここで一区切りです。

次回からは、Laravelアプリの品質を支える「テスト」編に入っていきます。

→ 次の記事:PHPUnitの基本:Laravelでテストを書く準備

タイトルとURLをコピーしました