こんにちは、かつコーチです。
前回はパスワードハッシュ化(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でテストを書く準備