[Laravel6.x]認可について ~備忘録~

##はじめに
この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。

今回は、Laravelの認可について色々と学んだので、すぐに見返すことができるように備忘録としてまとめておきたいと思います。

間違いなどがございましたら、ご指摘のほどよろしくお願い致します。


##"認可"とは
認可には、認証に基づく認可と認証から切り離した認可が存在します。
大抵の場合、認証に基づく認可であるケースが多い為、認証と認可は依存し合っているというイメージが強いと思われます。

###認証に基づく認可とは
Laravelにおける``認可``とは、**特定の条件に対して、どの様な動作を許可するかを定義する**ことを指します。
要するに、**認証したユーザに対して、具体的にどの様な動作を許可するかを定義する**ということです。

公式ドキュメントでは、**特定のリソースに対するユーザーアクションを認可する簡単な手法**と書かれています。

上記のように、「特定の条件」が「特定の誰かであることが認証されている」という条件であることが多い為、認可は認証に依存しているケースが多くなっています。

###認証から切り離した認可とは
認証から切り離した認可を考える際によく挙げられる例として、「チケットによる認可」があります。

チケットによる許可で重要なのは、特定の条件(誰か)であるから特定の動作をする許可を与えるのではなく、チケットを持っていることで特定の動作をする許可が与えられるという点です。
**特定の条件(特定の誰か=対象)とは関係なく、チケットを持っていることで特定の動作をする許可が与えられる**ということが、認証から切り離した認可となります。

##Laravelで"認可"を設定する方法
公式ドキュメントによると、Laravelの認可のアプローチはシンプルで、主に２つの認可アクションの方法として``Gate``と``Policy``というものが存在すると書かれています。

###・Gateとは
Gateとは、公式ドキュメントで下記のように書かれています。
>
ゲートは、特定のアクションを実行できる許可が、あるユーザーにあるかを決めるクロージャのことです。<br>通常は、App\Providers\AuthServiceProviderの中で、Gateファサードを使用し、定義します。
[Laravel6.x 認可 ゲート](https://readouble.com/laravel/6.x/ja/authorization.html#ポリシー作成)

``特徴``
・Gateを使用してアクションを認可する際は、メソッドに現在認証中のユーザーを渡す必要がない
・単純な論理値を返すGateだけではなく、エラーメッセージを含む詳細なレスポンスを返せる
・特定のモデルに関連していないユーザのアクションに対してアクセス制限を与える時に使用するケースが多い
などが挙げられます。

``重要``
公式ドキュメントでも記載されているように、主に**モデルやリソースとは関連しないアクションに対して**、Gateは適用されます。
要は、GateとPolicyを使い分けて認可を定義するということです。

###定義の仕方

Gateは``App\Providersフォルダ``の下にある``AuthServiceProvider.php``の中にアクセス制限を行うための定義を記述します。
定義を行う時はbootメソッドの中にGateファサードの``defineメソッド``を使用して定義します。


```AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
//略
public function boot()
{
$this->registerPolicies();

Gate::define('isAdmin', function ($user) {
        return $user->role == 'administrator';
    });
}
}
```
上記の例のように、defineメソッドの第一引数にはアクセス制限を行う際に利用する任意の名前、第二引数にはクロージャ―を設定します。
クロージャ―では基本的にtrueかfalseかを戻します。上記の例では、roleがadministratorであるかどうかを判別する処理を記述し、roleがadministratorであればtrueを戻し、異なればfalseを戻します。

定義を行なった後に、アクセス制限を行いたい箇所に追加でコードを記述していきます。

###Gateで定義した認可の使用方法
``・bladeファイルによるアクセス制御``
Gateの設定後にbladeファイルでアクセス制限を行う場合は、``@canや@cannot``を利用します。

```example.blade.php
@can('isAdmin')
// ユーザ一覧を表示するhtmlを記述
@elsecan
<p>管理者のみユーザ一覧を閲覧できます。</p>
@endcan

//or

@cannot('isAdmin')
// ユーザ一覧を表示するhtmlを記述
@elsecannot
<p>管理者以外がユーザ一覧を閲覧できます。</p>
@endcannot
```

``・コントローラーによるアクセス制御``
コントローラーの中でアクセス制限を行う場合は、authorizeメソッド、allowsメソッド、deniesメソッド等を利用します。

```ExampleController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use Gate;

class UserController extends Controller
{
//allowsの場合(deniesはallowsと逆の処理を行う)
if (Gate::allows('idAdmin')) {
    $users = User::all();
}
else{
dd('管理者のみユーザー一覧を表示できます。');
}
return view('users.index',compact('users'));

//authorizeの場合
public function index(){

Gate::authorize('isAdmin');

$users = User::all();

return view('users.index',compact('users'));
}
}
```
``補足``
・allowsメソッドに現在認証中のユーザーを渡す必要はなく、Laravelが自動的にGateクロージャにユーザーを渡します。
・authorizeメソッドを使用した場合、条件が不一致の際は自動的に**403のAuthorizationException**がthrowされます。

他にも、特定のユーザーがあるアクションを実行できる認可を持っているかを確認するメソッドや、複数のアクションの許可を一度に指定できるメソッドなどもある為、詳しくは[公式ドキュメント](https://readouble.com/laravel/6.x/ja/authorization.html)をご確認ください。

###・Policyとは
Policyとは、公式ドキュメントで下記のように書かれています。

>
ポリシーは特定のモデルやリソースに関する認可ロジックを系統立てるクラスです。
[Laravelドキュメント　認可　ポリシー作成](https://readouble.com/laravel/6.x/ja/authorization.html#creating-policies)

``特徴``
・Policyでは特定のモデルに関連する処理に対してアクセス制限を行ないます。

``重要``
全てのポリシーはLaravelのサービスコンテナにより依存解決されるため、ポリシーのコンストラクタに必要な依存をタイプヒントすれば、自動注入されます。

###定義の仕方
``make:policy Artisanコマンド``を使用し、ポリシーを生成できます。生成したポリシーは``app/Policiesディレクトリ``に設置されます。下記コマンドを実行して下さい。

```
php artisan make:policy ExamplePolicy
```
基本的な「CRUD」ポリシーメソッドを生成するクラスへ含めたい場合は、コマンド実行時に``--model``を指定してください。

```
php artisan make:policy ExamplePolicy --model=Example
```
コマンド入力後、下記のようなクラスを作成して下さい。(ドキュメントから引用)
必要に応じて認可するアクションごとにメソッドを追加します。
Policyを``--modelオプション``を付け、Artisanコマンドにより生成した場合、viewAny、view、create、update、delete、restore、forceDeleteアクションが含まれています。

```app/Policies/PostPolicy.php
class PostPolicy
{
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}
```
###Policyの登録
ポリシーを作成後、``AuthServiceProviderのpoliciesプロパティ``に登録します。

```AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
　/**
　　　* アプリケーションにマップ付されたポリシー
　　　*
　　　* @var array
　　　*/
    protected $policies = [
        'App\Models\Post' => 'App\Policies\PostPolicy',
    ];
}
```

``~注意点~``
Laravelでは、モデルとポリシーの標準命名規則にしたがっているポリシーを自動的に検出します。
自動検出が行われる対象となるのは、**モデルが含まれているディレクトリ下に存在する、PoliciesディレクトリのPolicy**です。
例えば、モデルがappディレクトリ下にあれば、Policyはapp/Policiesディレクトリへ置く必要があります。また、Policyの名前は対応するモデルの名前へ、Policyサフィックスを付けたものにする必要があります。従って、例えばUserモデルに対応させるには、UserPolicyクラスと命名します。

###Policyで定義した認可の使用方法
Policyはコントローラーやmiddlewareで、``canやcant``を呼び出して利用します。

``・Userモデルのcanメソッドによるアクセス制限``
Laravelアプリケーションに含まれるUserモデルは、アクションを認可するために``can``と``cant``というメソッドを持っています。
Controllerクラス内で``canメソッド``は**認可したいアクションと関連するモデルを引数に取ります。**

下記では、ユーザーが指定したPostの更新を認可するかを記述しています。
指定するモデル(Post)に対するポリシー(updateメソッド)が登録済みであれば適切なポリシーに対するcanメソッドが自動的に呼びだされ、**論理型**の結果が返されます。


```PostController.php
public function update(Post $post){

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

    if($user->can('update',$post)){
        //略
    	return view('posts.show')->with('msg_success', '更新が成功しました');

    }else{
        dd('更新する権限がありません。');
    }
}
```
<br>
``・authorizeメソッドによるアクセス制限``
``App\Http\Controllers\Controllerベースクラス``を拡張しているコントローラに対し、Laravelは``authorizeメソッド``を提供しています。
canメソッドと同様に、このメソッドは**認可対象のアクション名と関連するモデルを引数に取ります。**
アクションが認可されない場合、authorizeメソッドは``Illuminate\Auth\Access\AuthorizationException例外``を投げ、これはデフォルトでLaravelの例外ハンドラにより、403ステータスコードのHTTPレスポンスへ変換されます。

```PostController.php
class PostController extends Controller
{
 public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);
        // 現在のユーザーはブログポストの更新が可能
    }
}
```


<br>
``・ミドルウェアによるアクセス制限``
Laravelには、送信されたリクエストがルートやコントローラへ到達する前に、アクションを認可できるミドルウェアがあります。
デフォルトで``App\Http\Kernelクラスの中でcanキー``に``Illuminate\Auth\Middleware\Authorizeミドルウェア``が割り付けられています。

下記の例はドキュメントから引用しています。
>
この例では、canミドルウェアへ２つの引数を渡しています。最初の引数は認可したいアクションの名前です。２つ目はポリシーメソッドに渡したいルートパラメータです。この場合、暗黙のモデル結合を使用しているため、Postモデルがポリシーメソッドへ渡されます。ユーザーに指定したアクションを実行する認可がない場合、ミドルウェアは403ステータスコードのHTTPレスポンスを生成します。
[Laravelドキュメント 認可](https://readouble.com/laravel/6.x/ja/authorization.html#via-middleware)

```web.php
use App\Post;

Route::put('/post/{post}', function (Post $post) {
    // 現在のユーザーはこのポストを更新できる
})->middleware('can:update,post');
```
<br>
``・リソースコントローラにおけるアクセス制限``
リソースコントローラを利用している場合、コントローラのコンストラクタの中で、``authorizeResourceメソッド``を使用できます。
このメソッドはリソースコントローラのメソッドへ適切なcanミドルウェア定義を付加します。
authorizeResourceメソッドは、**最初の引数にモデルのクラス名**を指定し、第二引数にはモデルのIDを含むルート／リクエストパラメータ名を指定します。

```PostController.php
class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}
```


##補足
###・GateとPolicyの使い分け

私は、特定のモデルやリソースに関するものは``Policy``を利用し、それ以外は``Gate``を利用するようにしています。

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

・``Policy``は、特定のモデルやリソースに対して行うアクション(作成、更新、削除、閲覧等）に関してアクセス制限を行う際に使用します。

###・詳細なレスポンスを返す
GateやPolicyで単純な論理値を返す代わりに、``Response::allow() / Response::deny()``を使い、
呼び出し側では``Gate::inspect()``を呼び出すことで、Policyから認可レスポンスを受け取ることができます。

GateやPolicyメソッドから``Illuminate\Auth\Access\Response``を返すように設定して下さい。

``Gateの場合``

```AuthServiceProvider.php
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('update', function ($user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::deny('更新の権限が与えられていません');
});
```

```PostController.php
$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    //許可
} else {
    echo $response->message();
}
```
``Policyの場合``

```app/Policies/PostPolicy.php
use Illuminate\Auth\Access\Response;

public function update(User $user, Post $post)
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('更新の権限が与えられていません');
}
```

```PostController.php
$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    //許可
} else {
    echo $response->message();
}
```

###・モデルインスタンスを必要としない認可
createのようにモデルインスタンスを必要としない場合があります。
そのような場合は、モデルインスタンスの代わりに別のものを引数に渡します。

```example.php
//Policyメソッドを定義する際のcreateの場合
//モデルインスタンスの代わりに、その認証済みユーザーが期待している人物かをメソッドで定義します
//HTTPリクエストが認証済みユーザーにより開始されたものでなければ、全てのゲートとポリシーは自動的にデフォルトとしてfalseを返します。
class ExamplePolicy
{
public function create(User $user)
 {
    //
 }
}

//Policyを利用したアクションの認可の場合
//各コントローラのメソッド内の、$user->canでPolisyのcreateメソッドを使う場合はクラス名を渡します。
//クラス名はアクションを認可するときにどのポリシーを使用すべきかを決めるために使われます。
use App\Post;

public function create(Request $request)
{
   if ($user->can('create', Post::class)) {
    // 関連するポリシーの"create"メソッドが実行される
 }
}

//コントローラヘルパによる認可の場合
//コントローラーにおけるauthorizeメソッドの場合、上記と同じく第二引数にクラス名を渡します。
public function create(Request $request)
{
    $this->authorize('create', Post::class);
    // 現在のユーザーはブログポストを生成できる
}

//ミドルウェアの場合
//ミドルウェアの第二引数にクラス名を渡します。
//クラス名はアクションを認可するときに、どのポリシーを使用するかの判断に使われます。
Route::post('/post', function () {
    // 現在のユーザーはポストを更新できる
})->middleware('can:create,App\Post');
```
##参考文献
[Laravel6.x 公式ドキュメント 　認可](https://readouble.com/laravel/6.x/ja/authorization.html)
[Laravel GitHub](https://github.com/laravel/laravel)