🦓

[Laravel]モデルの認可ポリシーを作る

2023/10/16に公開

はじめに

ポリシーとは、特定のモデルに対してアクセス権(create,show,edit,update,destroyなど)を設定するためのLaravelの機能です。認可とも呼ばれます。

認可はGate(ゲート)とPolicy(ポリシー)という2つのアクションのを提供しています。
今回はポリシーについて見ていきます。

https://laravel.com/docs/10.x/authorization#creating-policies

環境

PHP 8.x
Laravel 10.x
MySQL 8.x

前提

UserモデルとListingモデルを例に進めていきます。
ログインしたユーザーが複数のリスティングにCRUDアクションを行うことができます。
リスティングの投稿者のみリスティングに対して編集、更新、削除することができます。

1. UserテーブルとListingテーブルが作成されている
2. Listingテーブルにforeign_key制約で参照するUserテーブルのカラムを追加されている

$table->foreignId('user_id')->constrained()->onDelete('cascade');

3. ユーザーと投稿の関連付けを設定されている

モデルの関連付け
app/models/User.php
<?php

namespace App\Models;

use App\Models\Listing;

class User extends Authenticatable
{
...
    public function listings()
    {
        return $this->hasMany(Listing::class);
    }
...
}
app/models/Listing.php
<?php

namespace App\Models;

use App\Models\User;

class Listing extends Model
{
...
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

tl;dr

  1. ポリシーを作成する
  2. ポリシーを設定する
  3. ポリシーを登録する
  4. Listingコントローラーでポリシーを使用する
  • authorizeメソッド
  • canメソッド
  • middleware(ミドルウェア)

ポリシーを作成する

➜    php artisan make:policy ListingPolicy --model=Listing

   INFO  Policy [app/Policies/ListingPolicy.php] created successfully.  
初期のListingPolicy.php
app/policies/ListingPolicy.php
<?php

namespace App\Policies;

use App\Models\Listing;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class ListingPolicy
{
    /**
     * Determine whether the user can view any models.
     */
    public function viewAny(User $user): bool
    {
        //
    }

    /**
     * Determine whether the user can view the model.
     */
    public function view(User $user, Listing $listing): bool
    {
        //
    }

    /**
     * Determine whether the user can create models.
     */
    public function create(User $user): bool
    {
        //
    }

    /**
     * Determine whether the user can update the model.
     */
    public function update(User $user, Listing $listing): bool
    {
        //
    }

    /**
     * Determine whether the user can delete the model.
     */
    public function delete(User $user, Listing $listing): bool
    {
        //
    }

    /**
     * Determine whether the user can restore the model.
     */
    public function restore(User $user, Listing $listing): bool
    {
        //
    }

    /**
     * Determine whether the user can permanently delete the model.
     */
    public function forceDelete(User $user, Listing $listing): bool
    {
        //
    }
}

ポリシーを設定する

コントローラーのアクションに応じてメソッドを追加していきます。

Controller Policy
index viewAny
show view
create create
store create
edit update
update update
destroy delete
app/policies/ListingPolicy.php
<?php

namespace App\Policies;

use App\Models\Listing;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class ListingPolicy
{
    public function update(User $user, Listing $listing): bool
    {
        // ユーザーがリスティングを編集・更新できるかどうかを確認
        return $user->id === $listing->user_id;
    }
    
    public function delete(User $user, Listing $listing): bool
    {
        // ユーザーがリスティングを削除できるかどうかを確認
        return $user->id === $listing->user_id;
    }
}

ポリシーを登録する

App\Providers\AuthServiceProviderに作成したポリシーを登録する必要があります。

app/Providers/AuthServiceProvider.php
<?php

namespace App\Providers;

// use Illuminate\Support\Facades\Gate;
use App\Models\Listing;
use App\Policies\ListingPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{

    protected $policies = [
        Listing::class => ListingPolicy::class,
    ];
...
}

ポリシーを使用する

コントローラーまたはビューでポリシーを使用することで、ユーザーが編集、更新、削除のアクションを実行する際アクセス制御を行います。

authorizeメソッド

app/Http/Controllers/ListingController.php
public function update(Request $request, Listing $listing)
{
    $this->authorize('update', $listing);

    // ユーザーはリスティングを編集できることが確認されました
    // ここに更新のロジックを追加
}

authorizeの第一引数のupdateListingPolicy.phpファイル内に記述したupdateメソッドを対応します。
第二引数には、Listingモデルの変数$listingを指定しています。

canメソッド

canメソッドを使う場合、auth()ヘルパーを使って現在のユーザーを取得する必要があります。

public function update(Listing $listing){

    $user = auth()->user(); //ユーザ情報を取得

    if($user->can('update',$listing)){
	...
    }else{
	...
    }

}

アクセス権があるかどうかでビューの表示を切り替えたい場合@canディレクティブを使うことができます。

@can('update', $listing)
    // 編集ボタンーを表示するなど
@else
    //
@endcan

https://laravel.com/docs/10.x/authorization#via-blade-templates

ミドルウェアによる制限

ファイルのルーティングでmiddlewareの設定を追加してアクセスを行うこともできます。

routes/web.php
use App\Models\Listing;

Route::put('/listings/{listing}', function (Listing $listing) {
    // The current user may update the post...
})->middleware('can:update,listing');

レスポンスをカスタマイズする

ユーザーが権限を持っている場合のみ操作することができるようになりました。
そうでない場合403エラーのページが表示されます。

レスポンスの表示をカスタマイズしたい場合、ポリシーの設定を追加できます。

public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('権限がありません。');
}

終わりに

モデルの認可ポリシーを作ってアクセス制限することができました!

Discussion