認証と認可(ポリシー編)

ポリシーでの認可

ポリシーとは

ポリシーとはモデルやリソースに対する認可ロジックを定義する機能です。コントローラーやルートから認可ロジックを分離し、コードを整理できます。コードの可読性が向上し、再利用性が高まり、認可ロジックを一か所に集約できます。

認証の実装

ポリシーはモデルと紐づけて運用します。ポリシーを実装する前に紐づけるモデルを用意します。今回のポリシーはユーザーが投稿した記事に対し編集を行う場合、投稿者本人しか編集できないようにするものとします。まずは、投稿のモデルを用意します。投稿モデルは、外部キーとしてユーザーの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アプリにアクセスして、挙動を確認します。まず、ログインしていない状態でデータの作成をします。すると、作成ボタンをクリックした後、ログイン画面にリダイレクトされます。次に、ログインしてデータを作成し、データの更新を行います。これは、成功するバズです。次に、別のアカウントでログインし、データの更新を行います。更新ボタンをクリックした後、ホーム画面にリダイレクトされます。これは、削除でも同じ処理になるはずです。以上のことが確認できれば、ポリシーの作成と運用は成功です。

今回はポリシーで認可する方法について解説しました。認可はセキュリティ上非常に重要な要素です。次回は認可に使用する、ミドルウェア、ゲート、ポリシーの違いをまとめて解説します。

前:

次;

コメントを残す

CAPTCHA