Laravel 認証と認可


はじめに

PHPUnitを実行していた時にPolicyの書き方がまずくthis action is unauthorized
というエラーが出て2時間ほど詰まっていました。
この件は無事解決したのんですが、調べていく内にLaravelにおいては認証と認可があるということに気づきました。
まったく意識せずにコードを書いていたので自身の知識定着を兼ねて2つの違いを確認していく。

ざっくりとした違い

2つの違いについてはこちらの記事を参考にさせていただきました。わかりやすい!
よくわかる認証と認可 | DevelopersIO

一部抜粋しますと
認証とは通信の相手が誰(何)であるかを確認すること
認可とはとある特定の条件に対して、リソースアクセスの権限を与えること

認可の相手が誰であるか?というのは身近なところで行くとマイナンバーカードとかの身分証ですね。
認証は電車の切符みたいなものです。相手が誰だろうと関係ありません、持ってさえいれば誰でも大丈夫です。

なんとなくわかったところでLaravelに話を戻すと
Laravelでは、認証が「Guard」、認可が「Gate/Policy」の3パターンあるのです。

認証:Guard

config/auth.php
    'guards' => [
        'guard-name' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],
route/web.php
Route::get('profile', function() {
})->middleware('auth:guard-name');

例としてはguardsを設定してそれをルートのmiddlewareで使うという感じですね。
Guardに設定されているユーザだけにアクセスを許可してます!

認可 Gate Policy

Policyはある特定のモデルに対して行うアクション(作成、更新、削除、閲覧等)に関してアクセス制限を行うため、モデルごとに個別の独立したPolicyファイルを作成する。

Gateは”主に”特定のモデルに関連していないユーザのアクションに関してアクセス制限を行う時に使用する。
例えば管理者画面にアクセスできるユーザを制限させるといったことにGateを利用することができます。

どちらか一方を使うというよりは、場面ごとに使い分けるってことですね!

Gate

ここのroleがadminかどうかで管理者を見分けていく。

users.table
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();
    });
}

まずはGateから考えてゆく。
定義を行う時はbootメソッドの中にGateファサードのdefineメソッドを使って行う。
defineメソッドの第一引数にはアクセス制限を行う際に利用する任意の名前、第二引数にはクロージャ―を設定します。クロージャ―の処理ではtrueかfalseかを戻す。
今回の場合はroleがadministratorであるかどうかを判別する処理を記述し、roleがadministratorであればtrueを戻し、そうでなければfalseを戻しとる。

AuthServiceProvider.php
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で行う制御

Gateの設定後にbladeファイルでアクセス制限を行う場合は、@canを利用する。

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

@cannotにすれば反対になる!

コントローラで行う制御

UsersContoroller.php
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'));

    }

}

結構直感的に理解できますね。
他にもallowを使うことができる

UsersContoroller.php
public function index(){

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

        $users = User::all();

    }else{

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

    }

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

}

ちなみにauthorizeメソッドを使用した場合は、roleがadministratorでない場合は403のAuthorizationExceptionが返される。

Policyを用いた制限

ターミナル $ php artisan make:policy HogePolicy --model=Postで作成可能。
--modelオプションはそのモデルに関するPolicyを作成するってこと。
つけなくてもおけい。
今回はPostPolicyを作成してみていく。

PostPolicy.php
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.phpに登録を行う必要がある。
登録を行うことでPostモデルとPostPolicyが紐づけられる!

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

Policyは3つのやり方があるばい。
①authorizeメソッドを利用した制限
②canメソッドを利用した制限
③middleware(ミドルウェア)を利用した制限

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

PostController.php
public function destroy(Post $post){

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

    $post->delete();

   return redirect('/posts');

}

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

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

PostController.php
public function show(Post $post){

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

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

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

    }else{

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

}

両者の違いは認証されなかった時に403ページが表示されるかされないか。

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

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

ちなみにコントローラメソッドとポリシーメソッドは関係性を持ってて
コントローラーのshowメソッドは、Policyのviewメソッドと関連を持ってて、コントローラのshowメソッドの中でauthorizeメソッドを実行すると自動でPostPolicy.phpファイルのviewメソッドが実行される。
他のやつもね。

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

PostController.php
class PostController extends Controller
{

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

まとめ

まあとりあえず
認証は通信の相手が誰(何)であるかを確認すること (Guard)
認可はとある特定の条件に対して、リソースアクセスの権限を与えること (Gate,Policy)

それぞれ定義してからbladeかcontrollerで使っていくと考えたら良いかなと思います。
習うより慣れろ!ですね。