ポリシーでの認可
ポリシーとは
ポリシーとはモデルやリソースに対する認可ロジックを定義する機能です。コントローラーやルートから認可ロジックを分離し、コードを整理できます。コードの可読性が向上し、再利用性が高まり、認可ロジックを一か所に集約できます。
認証の実装
ポリシーはモデルと紐づけて運用します。ポリシーを実装する前に紐づけるモデルを用意します。今回のポリシーはユーザーが投稿した記事に対し編集を行う場合、投稿者本人しか編集できないようにするものとします。まずは、投稿のモデルを用意します。投稿モデルは、外部キーとしてユーザーのidが指定できるようにしてあれば、それ以外特に指定はありません。今回は以下のようなモデルを作成します。
カラム名 | 内容 |
user_id | 記事を投稿したユーザーのID |
title | 記事のタイトル |
body | 記事の本文 |
次にモデルに紐づけたポリシーを作成します。以下のコマンドで作成します。
php artisan make:policy ArticlePolicy --model=Article
上記のコマンドを実行すると、app/Policies/ArticlePolicy.phpが作成されます。作成されたファイルの中身は以下の通りです。
<?php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ArticlePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return false;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Article $article): bool
{
return false;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Article $article): bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Article $article): bool
{
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Article $article): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Article $article): bool
{
return false;
}
}
各メソッドは表示、作成、更新、削除といった一通りの処理に対し、許可するかしないかを設定することができます。各メソッドの内容は以下の通りです。
メソッド | 内容 |
viewAny | モデルのリストを表示する権限をチェック |
view | 特定のモデルインスタンスを表示する権限をチェック |
create | 新しいモデルインスタンスを作成する権限をチェック |
update | 特定のモデルインスタンスを更新する権限をチェック |
delete | 特定のモデルインスタンスを削除する権限をチェック |
restore | ソフトデリートされたモデルを復元する権限をチェック |
forceDelete | 物理削除する権限をチェック |
ArticlePolicyは以下のようにしました。
<?php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ArticlePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Article $article): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Article $article): bool
{
return $article->user_id === $user->id;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Article $article): bool
{
return $article->user_id === $user->id;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Article $article): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Article $article): bool
{
return false;
}
}
記事の一覧表示、個別記事の表示、記事の作成は誰でもできるようにしています。記事の更新と削除は記事の投稿者のみ可能としています。ソフトデリートと物理削除の機能は搭載していないので、この部分についてはデフォルトのままにしています。
次に、app/Providers/AuthServiceProvider.phpにポリシーを登録します。以下のようにポリシーを追加し、登録します。
protected $policies = [
Article::class => ArticlePolicy::class
];
policiesでモデルとポリシーを紐づけ、registerPoliciesメソッドでポリシーを登録します。
次に、コントローラーを作成します。コントローラーはリソースコントローラを使用しました。コントローラーは以下のようになります。
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$articles = Article::all();
return view("article.index", compact("articles"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view("article.create");
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
if (!$request->user()) {
return redirect(route("login"));
}
$validatedData = $request->validate([
"title" => "required|string",
"body" => "required|string",
]);
$article = new Article();
$article->user_id = $request->user()->id;
$article->title = $validatedData["title"];
$article->body = $validatedData["body"];
$article->save();
return redirect(route("article.index"));
}
/**
* Display the specified resource.
*/
public function show(Article $article)
{
return view("article.show", compact("article"));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Article $article)
{
return view("article.edit", compact("article"));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Article $article)
{
if (!$request->user()) {
return redirect(route("login"));
}
if ($request->user()->can("update", $article)){
$validatedData = $request->validate([
"title" => "required|string",
"body" => "required|string",
]);
$article->user_id = $request->user()->id;
$article->title = $validatedData["title"];
$article->body = $validatedData["body"];
$article->save();
return redirect(route("article.index"));
} else {
return redirect("/");
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request, Article $article)
{
if (!$request->user()) {
return redirect(route("login"));
}
if ($request->user()->can("delete", $article)){
$article->delete();
return redirect(route("article.index"));
} else {
return redirect("/");
}
}
}
データの更新と削除はまず、$request->user()でユーザーがログインしているかを確認し、ユーザーがログインしていなければ、ログイン画面にリダイレクトします。次に、ユーザーがログインしていればcanメソッドで処理を実行する権限を持っているか確認し、権限を持っていれば実行、持っていなければホーム画面にリダイレクトしています。
実際にWebアプリにアクセスして、挙動を確認します。まず、ログインしていない状態でデータの作成をします。すると、作成ボタンをクリックした後、ログイン画面にリダイレクトされます。次に、ログインしてデータを作成し、データの更新を行います。これは、成功するバズです。次に、別のアカウントでログインし、データの更新を行います。更新ボタンをクリックした後、ホーム画面にリダイレクトされます。これは、削除でも同じ処理になるはずです。以上のことが確認できれば、ポリシーの作成と運用は成功です。
今回はポリシーで認可する方法について解説しました。認可はセキュリティ上非常に重要な要素です。次回は認可に使用する、ミドルウェア、ゲート、ポリシーの違いをまとめて解説します。
前:
次;