- カテゴリ: 認可(Authorization)
- 掲載バージョン: Laravel 13(Laravel 11以降ほぼ共通の挙動)・PHP 8.4
- 関連クラス / コマンド:
php artisan make:policy/Illuminate\Support\Facades\Gate/Illuminate\Auth\Access\Response - 関連: Laravel Gate / Laravelのアクセス制御 / abort関数
- 変更履歴: Laravel 11で
AuthServiceProviderが既定のスケルトンから削除され、命名規則によるPolicyの自動検出が標準に。Laravel 12で#[UsePolicy]属性が、Laravel 13でGate::guessPolicyNamesUsingなど検出ロジックのカスタマイズがそれぞれ利用可能。
要点(TL;DR)
- PolicyはEloquentモデルなど特定のリソースに対する認可ロジックをまとめるクラス。生成は
php artisan make:policy PostPolicy --model=Post - Laravel 11以降は命名規則が合っていれば登録不要(
App\Models\Post→App\Policies\PostPolicyを自動検出)。手動登録が必要ならAppServiceProviderのboot()でGate::policy(Post::class, PostPolicy::class) - 呼び出しは
$request->user()->can('update', $post)/ コントローラの$this->authorize('update', $post)/ Bladeの@can('update', $post)/ ルートミドルウェアの->middleware('can:update,post')のいずれか - よくある罠:
- 新規プロジェクトに
AuthServiceProviderが存在しない(Laravel 11以降)。古い記事の手順どおりに探すと迷子になる - Policyクラス名・配置ディレクトリが規約とズレていると自動検出されず、権限チェックが常に
falseになる - 未ログイン(ゲスト)ユーザーはメソッド引数を
?User $userにしないと呼び出し前に自動で拒否される
- 新規プロジェクトに
概要:LaravelにおけるPolicyとは
Policyは、特定のEloquentモデルやリソースに関連付けて認可ロジックを整理するためのクラスです。Laravelの認可機能にはGateとPolicyの2種類があり、公式ドキュメントでは「Gateはルート、Policyはコントローラのようなもの」と例えられています。管理画面の表示可否のようにモデルに紐づかないアクションはGate、投稿の編集・削除のように特定のモデルインスタンスに対する権限判定はPolicyという使い分けが基本です。
1つのPolicyクラスにviewAny・view・create・update・delete・restore・forceDeleteといったCRUD相当のメソッドをまとめて定義できるため、コントローラやBladeテンプレートに散らばりがちなif文の権限判定を1か所に集約でき、テストもしやすくなります。
Policyの作成
make:policy Artisanコマンドで生成します。存在しなければapp/Policiesディレクトリも自動作成されます。
# 空のPolicyクラスを生成
php artisan make:policy PostPolicy
# viewAny/view/create/update/delete/restore/forceDeleteの雛形付きで生成
php artisan make:policy PostPolicy --model=Post
--modelオプションを付けると、各アクションに対応するメソッドの雛形(引数の型ヒント込み)が自動で入るため、CRUD一式を実装する場合はこちらが効率的です。
Policyの登録:自動検出と手動登録
自動検出(既定・推奨)
Laravel 11以降、新規プロジェクトのスケルトンからAuthServiceProvider自体が削除されています。命名規則(モデルと同じ階層以上にあるPoliciesディレクトリに、モデル名+Policyというクラス名)に従っていれば、登録コードを書かなくてもLaravelが自動でPolicyを見つけます。
| モデル | 期待されるPolicyクラス |
|---|---|
App\Models\Post | App\Policies\PostPolicy |
App\Models\User | App\Policies\UserPolicy |
検出ロジック自体を変更したい場合は、AppServiceProviderのboot()でGate::guessPolicyNamesUsing()にコールバックを渡してカスタマイズできます。
手動登録(Gate::policy)
命名規則から外れる場合や明示的に登録したい場合は、AppServiceProviderのboot()メソッドでGate::policy()を呼び出します(旧来の記事に出てくるAuthServiceProviderではない点に注意)。
// app/Providers/AppServiceProvider.php
use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::policy(Order::class, OrderPolicy::class);
}
属性で登録(#[UsePolicy])
モデルクラス自体に属性を付けて、対応するPolicyを明示することもできます。
use App\Policies\OrderPolicy;
use Illuminate\Database\Eloquent\Attributes\UsePolicy;
use Illuminate\Database\Eloquent\Model;
#[UsePolicy(OrderPolicy::class)]
class Order extends Model
{
//
}
Policyメソッドの書き方
Policyクラスはサービスコンテナ経由で解決されるため、コンストラクタで依存を型ヒントすれば自動注入されます。
| メソッド | 対応アクション | 引数の例 |
|---|---|---|
viewAny | 一覧表示 | User $user |
view | 個別詳細表示 | User $user, Post $post |
create | 新規作成 | User $user(モデルなし) |
update | 更新 | User $user, Post $post |
delete | 削除 | User $user, Post $post |
restore | ソフトデリートの復元 | User $user, Post $post |
forceDelete | 完全削除 | User $user, Post $post |
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;
}
// createはモデルインスタンスを受け取らない
public function create(User $user): bool
{
return $user->role === 'writer';
}
}
理由付きで拒否する(Response)
単純なboolの代わりにIlluminate\Auth\Access\Responseを返すと、拒否理由をエラーメッセージとしてそのまま画面や例外に伝播できます。ステータスコードを変えたい場合はdenyWithStatus()、404として隠したい場合はdenyAsNotFound()が使えます。
use Illuminate\Auth\Access\Response;
public function update(User $user, Post $post): Response
{
return $user->id === $post->user_id
? Response::allow()
: Response::deny('この投稿の所有者ではありません。');
}
未ログイン(ゲスト)ユーザーを許可する
既定では未認証リクエストのPolicy呼び出しは自動的にfalseを返します。ゲストにもチェックを通したい場合は、引数をnullable(?User $user)にします。
public function view(?User $user, Post $post): bool
{
// 未ログインでも公開投稿なら閲覧可、それ以外は所有者のみ
return $post->is_public || $user?->id === $post->user_id;
}
before()で管理者に全権限を与える
Policyクラスにbefore()メソッドを定義すると、他のどのメソッドよりも先に評価されます。true/falseを返すとその結果が最終判定になり、nullを返すと通常のメソッドの判定にフォールバックします。管理者への一括許可に多用されます。
public function before(User $user, string $ability): ?bool
{
if ($user->isAdministrator()) {
return true;
}
return null;
}
Policyを使う4つの方法
1. コントローラ(can / cannot / authorize)
use App\Models\Post;
use Illuminate\Http\Request;
public function update(Request $request, Post $post)
{
// 方法A: Userモデルのcan/cannot
if ($request->user()->cannot('update', $post)) {
abort(403);
}
// 方法B: コントローラのauthorizeヘルパ(未許可なら自動で403例外)
$this->authorize('update', $post);
// 更新処理...
}
public function store(Request $request)
{
// createのようにモデルを持たないアクションはクラス名を渡す
$this->authorize('create', Post::class);
// 作成処理...
}
2. Bladeテンプレート(@can / @cannot / @canany)
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">編集</a>
@elsecan('create', App\Models\Post::class)
<a href="{{ route('posts.create') }}">新規作成</a>
@endcan
@canany(['update', 'delete'], $post)
<!-- 更新・削除いずれかが可能な場合 -->
@endcanany
3. ルートミドルウェア(can:)
use App\Models\Post;
// 文字列指定
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
// メソッドチェーン版(可読性が高い)
Route::put('/posts/{post}', [PostController::class, 'update'])
->can('update', 'post');
4. 追加の引数を渡す
第2引数を配列にすると、先頭要素でPolicyを特定し、残りの要素がメソッドの追加引数として渡されます。
public function update(User $user, Post $post, int $categoryId): bool
{
return $user->id === $post->user_id
&& $user->canUpdateCategory($categoryId);
}
// 呼び出し側
$this->authorize('update', [$post, $request->category_id]);
テスト例(Pest)
use App\Models\Post;
use App\Models\User;
it('allows the owner to update their own post', function () {
$user = User::factory()->create();
$post = Post::factory()->for($user)->create();
expect($user->can('update', $post))->toBeTrue();
});
it('denies other users from updating a post', function () {
$owner = User::factory()->create();
$other = User::factory()->create();
$post = Post::factory()->for($owner)->create();
expect($other->can('update', $post))->toBeFalse();
});
よくある落とし穴・注意
AuthServiceProviderが見つからない:Laravel 11以降の新規プロジェクトには存在しません。手動登録が必要な場合はAppServiceProviderのboot()にGate::policy()を書きます。- 自動検出されない:モデルとPolicyのディレクトリ・命名規則(
App\Models\Xxx→App\Policies\XxxPolicy)がずれていると検出に失敗し、Gate側のフォールバックにも一致しなければ常に拒否されます。 - ゲストが弾かれる:未ログイン状態を許可したいメソッドは引数をnullable(
?User $user)にしないと、Policy本体が呼ばれる前に自動でfalse扱いになります。 before()が呼ばれないケース:チェック対象のアビリティ名に一致するメソッドがPolicyクラスに存在しない場合、before()自体がスキップされます。- createの引数ミス:
createのようにモデルインスタンスがまだ存在しないアクションは、can()やauthorize()にクラス名(文字列)を渡す必要があります。インスタンスを渡すコードをそのまま流用すると型エラーになります。
Gate と Policy の使い分け
| 観点 | Gate | Policy |
|---|---|---|
| 定義場所 | クロージャ(AppServiceProvider) | 専用クラス(app/Policies) |
| 向いている用途 | モデルに紐づかない単発の判定(管理画面表示など) | 特定モデルのCRUD権限をまとめて管理 |
| 登録 | Gate::define() | 自動検出 or Gate::policy() / #[UsePolicy] |
| 呼び出し方法 | Gate::allows() など | can() / authorize() / @can(内部的にGateへ委譲) |
実務ではどちらか一方だけを使うのではなく、モデル単位の権限はPolicy、それ以外の横断的な判定はGateという組み合わせが一般的です。
トラブルシュート(エラー別)
| 症状/エラー | 原因 | 対処 |
|---|---|---|
This action is unauthorized.(403) | PolicyメソッドがfalseまたはResponse::deny()を返した | 該当メソッドの条件を確認。理由文言を出したい場合はResponse::deny('メッセージ')を使う |
| Policyのメソッドが呼ばれず常に拒否される | クラス名・配置ディレクトリが命名規則から外れ自動検出に失敗 | Gate::policy(Model::class, Policy::class)で明示登録、または#[UsePolicy]属性を付与 |
Target class [App\Providers\AuthServiceProvider] does not exist | Laravel 11以降のスケルトンに存在しないクラスを古い手順どおり参照 | AppServiceProviderのboot()にGate::policy()を書く、または自動検出に任せる |
| ゲストアクセス時に想定外の403 | Policyメソッドの第1引数がUser型(非nullable) | 引数を?User $userにし、$user?->idのようにnullセーフに判定 |
create系だけ動かない | 呼び出し側でモデルインスタンスを渡している | can('create', Post::class)のようにクラス名を渡す |
よくある質問(FAQ)
Q. LaravelのPolicyとは何ですか?
特定のEloquentモデルやリソースに対する認可(そのユーザーがそのアクションを行ってよいか)のロジックを1つのクラスにまとめる仕組みです。投稿の編集・削除権限のように、モデルインスタンスごとに判定条件が変わる場合に向いています。
Q. PolicyとGateはどちらを使えばいいですか?
特定のモデル・リソースに紐づく判定(投稿の更新可否など)はPolicy、モデルに紐づかない単発の判定(管理ダッシュボードの表示可否など)はGateが基本方針です。両方を併用しても問題ありません。
Q. Policyを登録しなくても動くことがあるのはなぜですか?
Laravel 11以降は、モデルとPolicyの命名規則(App\Models\Xxxに対してApp\Policies\XxxPolicy)が一致していれば自動検出されるためです。命名や配置がずれている場合、あるいは検出ロジックを独自にしたい場合のみGate::policy()や#[UsePolicy]属性での明示登録が必要になります。
Q. 未ログインユーザーにもPolicyのチェックを通したい場合は?
Policyメソッドの第1引数をnullableな?User $userにします。既定では未認証リクエストは呼び出し前に自動でfalse判定されるため、ゲスト向けのロジックを書きたい場合は必須の対応です。
Q. 管理者だけ全操作を許可したい場合は?
Policyクラスにbefore(User $user, string $ability): ?boolを定義し、管理者ならtrue、それ以外はnullを返します。nullを返すと通常の各メソッドの判定に処理が続きます。
関連記事
- Laravel Gateの使い方:権限管理を理解してアプリを安全に
- Laravelのアクセス制御について知るべきポイントと実装ガイド
- Laravelのabort関数の使い方|404/403エラーページを簡単にカスタマイズ

コメント