LaravelのGate(ゲート)とPolicy(ポリシー)は名前も全く異なるため、別の機能だと思うかもしれませんがどちらもAuthorization(認可)に関する機能です。

認可はLaravelのドキュメントでは、”authorize user actions against a given resource”と説明されています。日本語では”あるリソースに対するユーザのアクションに許可を与える”という意味になるかと思います。

例えばブログの記事を作成したユーザに対して、削除許可を与えるのかどうかといった設定を行うことを認可といいます。

Gate(ゲート)とPolicy(ポリシー)という言葉が使われていますが、要はアクセス制限です。

GateとPolicyの違い

どちらも認可を与える機能ですが、Policyはある特定のモデルに対して行うアクション(作成、更新、削除、閲覧等)に関してアクセス制限を行います。また、モデルごとに個別の独立したPolicyファイルを作成しアクセス制限を行うことができます。Gateは”主に”特定のモデルに関連していないユーザのアクションに関してアクセス制限を行う時に使用します。例えば管理者画面にアクセスできるユーザを制限させるといったことです。

Gateもモデルに対するアクションにアクセス制限を行うことは可能です。Policy、Gateのどちらか一方を使うのではなくどちらも利用しながらアクセス制限を行います。

動作確認のための環境準備

実際にLaravelで構築したシステムを利用した方がわかりやすいので、UserモデルとPostモデルを作成して動作確認用の環境構築を行います。

UserテーブルとPostテーブルの作成

usersテーブルとpostsテーブルの構成情報の説明を行います。

usersの設定はLaravelの初期のmigrationファイルにroleの列を追加しています。roleを使用してそのユーザの役割を設定します。管理者であればadministratorのroleを設定します。


public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->string('role');
        $table->rememberToken();
        $table->timestamps();
    });
}

postsテーブルについては、ブログの記事を保存することを想定しているので、作成した作成者のuser_idと記事のタイトル(title)、内容(content)の3つの列を追加します。


public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->integer('user_id')->unassign();
        $table->string('title');
        $table->text('content');
        $table->timestamps();
    });
}

データの登録

作成したusesテーブルには5名のユーザを登録しています。

ユーザ一覧
ユーザ一覧

postsテーブルには4つの記事を登録しています。各記事にuser_idを設定しているので作成者の名前を表示させることができます。

記事一覧
記事一覧

アクセス制限とは

Gate, Policyを使用しなくてもアクセス制限を行うことはできます。アクセス制限とはどういうものかを確認するためにGate, Policyを使用せずに行なってみましょう。

記事の削除に対する制限

あるユーザがブログの記事を書いた場合そのユーザのみ削除する許可する権限を与えるとします。アクセス制限を与えるためには、2つの方法が考えられます。1つは削除許可がないユーザにはブラウザ上で削除ボタンを表示させない方法。もう一つは削除処理を行わせない方法です。実際にコードで確認していきます。

削除ボタンを非表示にする

削除許可がないユーザには削除ボタンを非表示にします。記事一覧にアクセスしているユーザのidと$postのuser_idを利用し、Bladeファイル内でif文の分岐を行えば削除ボタンの表示・非表示を制御することができます。

ユーザのidと$postのuser_idが一致する場合はボタンが表示され、一致しない場合はボタンは表示されません。


<td>
	@if(Auth::user()->id === $post->user_id)
	<form method="POST" action="/posts/{{ $post->id }}">
	@method('delete')
	@csrf
	<button type="submit" class="btn btn-danger">削除</button>
	</form>
	@endif
</td>

鈴木さんがアクセスした場合は鈴木さんが作成したID3の記事のみ削除ボタンが表示されます。

作成者以外は削除ボタンは表示されない
作成者以外は削除ボタンは表示されない

削除処理を行わせない

削除許可がないユーザに削除処理を行わなせないことで制限を行います。この場合はBladeファイルではなくコントローラー側で設定を行い、ユーザのidと$postのuser_idが一致した時のみ削除プログラムが実行されます。


public function destroy(Post $post){

        if(\Auth::user()->id == $post->user_id){

        	$post->delete();

        }else{
          // 削除する許可がない場合はなにか処理を行う

        }

    	return redirect('/posts');

    }

2つの例を通して、アクセス制限がどういうものか理解してもらえたのではないでしょうか。

Gate(ゲート)を利用したアクセス制限

Gateを使用してあるページへのアクセス制限を行います。ここではroleにadministratorのみをもつユーザにだけユーザ一覧のページを閲覧する権限を与えます。

GateはAuthServiceProviderを使用して設定を行います。

bootメソッドの中でGateファサードを使ってroleがadministratorであるかどうかを判別するクロージャを使って記述します。


namespace App\Providers;

use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy',
];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();


        Gate::define('isAdmin',function($user){

           return $user->role == 'administrator';
    
        });
    }
}

ここでは定義を行なっているだけなので、実際にアクセス制限を行う場合は、制限を行いたい箇所に追加でコードを記述する必要があります。

Bladeファイルで行う制御

bladeファイルでアクセス制限を行う場合は、@canを利用することができます。


@can('isAdmin')
  // ユーザ一覧を表示するhtmlを記述
@else
<p>管理者のみユーザ一覧が表示されます。</p>
@endcan

administratorのroleを持っているユーザにはユーザ一覧が表示されます。

ユーザ一覧
ユーザ一覧

administrorのroleを持っていないユーザにはユーザ一覧は表示されません。

アクセス制限のないユーザがアクセスした場合
アクセス制限のないユーザがアクセスした場合

@canだけではなく@cannot, @cantを使用することもできます。


@cannot('isAdmin')
  // ユーザ一覧を表示するhtmlを記述

@else
<p>管理者のみユーザ一覧が表示されます。</p>
@endcan

canの逆になるので、administratorのroleを持っていないユーザのみにユーザ一覧が表示されます。

コントローラーで行う制御

Controllerで制限を行いたい場合は、authorizeメソッド、allowsメソッド、deniesメソッド等を利用することができます。authorizeメソッドは下記のように記述することができます。


namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\User;

use Gate;

class UserController extends Controller
{

	public function index(){

	    Gate::authorize('isAdmin');

	    $users = User::all();

	    return view('users.index',compact('users'));
	    
	}

}

authorizeメソッドを使用した場合は、roleがadministratorでない場合は403のAuthorizationExceptionがthrowされます。

authorizeメソッドは次の処理の入り口のような役割をしていると考えばGate(ゲート)という名前をつけていることも理解できるような気がします。
403 unauthorized
403 unauthorized

allowsの場合は、AuthorizationExceptionがthrowされないので、if分により分岐処理を行うことができます。


public function index(){

    if(Gate::allows('isAdmin')){

		$users = User::all();

	}else{

		dd('ユーザ一覧にアクセスが許可されていないユーザです。');

	}

	return view('users.index',compact('users'));
    
}

deniesはallowsと逆の処理を行うことができます。deniesは設定した場合はadministratorのroleを持たないユーザがユーザ一覧を閲覧することができます。

forUserメソッドの使用方法

他のユーザのidを利用して削除のアクセス制限を行うためにforUserメソッドを使用することができます。

アクセスするユーザではなくforUserメソッドに他のユーザを指定することで指定したユーザの情報で削除のアクセス制限を行うことができます。


public function destroy(Post $post){

    $other_user = User::find(1); //アクセスしたユーザではなくid1を持つユーザ利用

    if(Gate::forUser($other_user)->allows('delete-post',$post)){

        $post->delete();

    }

	return redirect('/posts');

}

Policy(ポリシー)を用いたアクセス制限

policy(ポリシー)の作成

Gateの説明では特定のモデルに関連しない処理に対してアクセス制限を行いました。Policyでは特定のモデルに関連する処理に対してアクセス制限を行なっていきます。

Policyファイルはphp aritsan make:policyコマンドで作成することができます。実行するとapp¥policiesフォルダの下にPostPolicy.phpファイルが作成されます。


$ php artisan make:policy PostPolicy
Policy created successfully.

作成されるファイルの中身は下記の通りでメソッドは何も記述されていません。


namespace App\Policies;

use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;

    /**
     * Create a new policy instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }
}

–modelオプションをつけてPolicyを作成をするとPolicyで使用するメソッドが記述された状態でファイルを作成することができます。

コントローラーを作成する時にCRUDのメソッドを記述した状態で作成する–resourceオプションに似た動作です。コントローラーでは、index, create, store, show, edit, updateメソッドが記述されます。

 $ php artisan make:policy PostPolicy --model=Post
Policy created successfully.

記述されているメソッドを元にPostモデルに特化したPolicyを作成することができます。PostPolicy.phpファイルには、viewAny, view, create, update, delete, restore, forceDeleteの7つのメソッドが記述された形で作成されます。


namespace App\Policies;

use App\User;
use App\Post;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;
    
    /**
     * Determine whether the user can view any posts.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        //
    }

    /**
     * Determine whether the user can view the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function view(User $user, Post $post)
    {
        //
    }

Policyを利用したアクセス制限の準備

これから削除と閲覧に対してアクセス制限を行うため、PostPolicy.phpファイルの更新を行います。

使用しないメソッドはPostPolicy.phpファイルから削除することができます。ここではこの後利用するdeleteメソッドとviewメソッドのみを残して残りのメソッドは削除しています。どちらもメソッドもアクセスしているユーザidと記事を作成したユーザidが一致するかどうかのシンプルなコードです。



namespace App\Policies;

use App\User;
use App\Post;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;
 
    /**
     * Determine whether the user can view the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function view(User $user, Post $post)
    {
       
        return $user->id == $post->user_id;
  
    }
   
    /**
     * Determine whether the user can delete the post.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return mixed
     */
    public function delete(User $user, Post $post)
    {
        return $user->id == $post->user_id;
    }

}

作成したPolicyの情報はAuthServiceProviderに登録を行う必要があります。登録を行うことでPostモデルとPostPolicyが紐づけられます。


protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy',
];

AuthServiceProviderへの追加が完了したので、Policyを使ってアクセス制限を行います。いくつか異なる方法で設定を行うことができ、下記では3つの方法を紹介しています。

authorizeメソッドを利用した制限

authorizeメソッドを記述することでPolicyでアクセス制限を行うことができます。削除に関する制限なので、コントローラーのdestroyメソッドの中に記述しています。


public function destroy(Post $post){

    $this->authorize('delete', $post);

    $post->delete();

   return redirect('/posts');

}

authorizeの最初に引数の’delete’がPostPolicy.phpファイル内に記述したdeleteメソッドを対応します。2番目の引数には、Postモデルの変数$postを指定しています。

PostPolicy.phpの中で設定したdeleteメソッドの結果、アクセスしたユーザのidと記事を作成したユーザidが一致すればそのまま処理を行います。

しかし、ユーザidと記事の作成者のidが一致しない場合は下記のようにブラウザに403 unauthorizedが表示されます。

403 unauthorized
403 unauthorized

canメソッドを利用した制限

authorizeメソッドではなくcanメソッドも利用することができます。canメソッドを利用する場合は、$userを使ってcanメソッドを使用するためauth()ヘルパー関数等を利用してユーザ情報を取得しておく必要があります。


public function show(Post $post){

    $user = auth()->user(); //アクセスしているユーザ情報を取得

    if($user->can('view',$post)){

    	return view('posts.show',compact('post'));

    }else{

        dd('閲覧する許可がありません。');
    }

}

canメソッドではユーザのidと記事を作成したユーザのidが一致しない場合は、403 unauthorizedのExceptionがthrowされません。

canメソッドではなく逆の処理を行うcantメソッド、cannotメソッドがあります。

middleware(ミドルウェア)を利用した制限

middleware(ミドルウェア)を利用してアクセス制限を行うこともできます。下記ではweb.phpファイルのルーティングでmiddlewareの設定を行なっています。


Route::get('/posts/{post}','PostController@show')->middleware('can:view,post');

ユーザのidと記事を作成したユーザのidが一致しない場合は、403 unauthorizedのExceptionがthrowされます。

Policyのメソッドについて

–modelオプションを利用してPolicyを作成するとview, updateなどのメソッドが記述された形でPolicyファイルが作成されました。

これらのメソッドは、コントローラーのメソッドと関連を持っています。

PolicyとContollerメソッドの関係
PolicyとContollerメソッドの関係

コントローラーのshowメソッドは、Policyのviewメソッドと関連を持っているので、下記のようにコントロラーのshowメソッドの中でauthorizeメソッドを実行すると自動でPostPolicy.phpファイルのviewメソッドが実行されます。


public function show(Post $post){

    $this->authorize($post);

    return view('posts.show',compact('post'));

}

showメソッドの中でauthorizeメソッドを実行する際に引数にviewを入れなくても自動でviewメソッドが呼ばれます。

コントローラーの__constructメソッドにauthorizeResourceメソッドを追加するとコントローラーの個別のメソッドでauthorizeメソッドを使わなくてもPolicyファイルで指定したメソッドが有効になります。


class PostController extends Controller
{

	public function __construct(){
        $this->authorizeResource(Post::class);
	}

authorizeResourceメソッドを設定することでshowメソッドの中で個別にauthorizeメソッドを使っている場合は、その行を削除してもshowメソッドのアクセス制御を行うことができます。

アクセス制限の説明を行う際にGateやPolicyを使わない方法を紹介しました。その方法に比較して、GateやPolicyを使用すると効率よく設定が行えることが理解できたかと思います。これらの機能を使わずアクセス制限をしているのであればぜひ使用してみてください。