【Laravel】初学者による初学者のための認可機能（Policy）の使い方

## はじめに

例えば、あるサイトでユーザーID1の人がマイページのユーザー詳細画面を開いた時、URLが
`https://○○○○/user/1`
のような形だったとします。1というのはユーザーIDを指します。
もしここを2に変えた時、何が起こると思いますか？

**なんと、ユーザーID1の人がユーザーID2の人のユーザー詳細画面に入れてしまうんです！**

個人情報が全て筒抜けになってしまうんです。他にも、ユーザー情報の編集画面に入って他人の情報を書き換えることもできてしまったりします。

そんな危ないサイト使いたくないですよね。 

なので、このような場合は必ず認可機能を実装してアクセス制限を行うようにしましょう！！

※IDを推測されやすい番号にしない、といった内容は今回の記事では取り扱わないことにします。

## 環境

- Composer 1.10.20
- PHP 7.4.15
- Laravel 6.20.20


## Policyとは
 
Laravelでは認可のために方法がいくつか用意されています。
今回はその中からPolicyについてのみ説明しますが、詳しく知りたい方は以下の記事を読んでみてください。

- [Laravel Gate(ゲート)、Policy(ポリシー)を完全理解](https://reffect.co.jp/laravel/laravel-gate-policy-understand)
- [Laravel Document （Laravel 6.x 認可
）](https://reffect.co.jp/laravel/laravel-gate-policy-understand)

Policyとは、ある特定のモデルに対して行うアクション(作成、更新、削除、閲覧等）に関してアクセス制限を行う仕組みのことです。そのためモデルごとに個別の独立したPolicyファイルを作成する必要があります。

## 実装

ここから、実装の手順を説明していきます。

### 1. Policyの作成

まず、Policyファイルを作成します。
`–-model`オプションでモデルを指定してPolicyを作成をすると、Policyで使用するメソッドが記述された状態でファイルを作成することができます。
今回はUserモデルに対してPolicyを生成します。

```
$ php artisan make:policy UserPolicy　--model=User
```

これを実行すると、`app/Policies`ディレクトリの中に`UserPolicy.php`が作成されます。

```php:UserPolicy.php
namespace App\Policies;

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

class UserPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view any users.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function viewAny(User $user)
    {
        //
    }

    /**
     * Determine whether the user can view the user.
     *
     * @param  \App\User  $user
     * @param  \App\User  $user
     * @return mixed
     */
    public function view(User $user, User $user)
    {
        //
    }
}
```
※本来は他にcreate, update, delete, restore, forceDeleteのメソッドが記述された形で作成されますが、省略しています。

今回はviewメソッドを使用しますが、当時僕はここで「ん？」となりました。

`User $user, User $user`ってどういうこと？同じじゃないの？？

ここの説明と記述は後ほどします。

###2. Policyの登録

Policyを作成したら、登録する必要があります（省略可※後述）

`app/Providers`ディレクトリ内の`AuthServiceProvider.php`を編集します。

```php:AuthServiceProvider.php
protected $policies = [
    'App\User' => 'App\Policies\UserPolicy',
];
```

#### ※ Policyの自動検出

Laravel 5.8 で追加された機能です。
モデルとポリシーの標準命名規則に従っているポリシーをLaravelが自動的に見つけてくれる便利な機能で、この場合はPolicyの登録を省略できます。

例えば、`app`ディレクトリ下にモデルがある場合、`app/Policies`ディレクトリ下のPolicyファイルを自動検出してくれます。

この時、モデルの名前に対応させたPolicyファイルを作成する必要があります。
（今回の場合はUserモデルに対するPolicyなので、`UserPolicy.php`という名前をつけます。）

### 3. コントローラーの編集

次に、マイページ表示の処理を行っているshowアクションに`authorize`メソッドを追記していきます。

```php:UserController.php
public function show(User $user)
{
    $this->authorize('view', $user);//追記

    return view('mypage');
}
```

`authorize`メソッドの最初の引数を`view`、2番目の引数をUserモデルの変数`$user`とすることで、`UserPolicy.php`の`view`メソッドに`$user`の情報を渡します。

アクションが認可されない場合、`authorize`メソッドは403エラーを返します。

### 4. Policyの編集

`UserPolicy.php`に戻って、`view`メソッドを編集していきます。

```php:UserPolicy.php
public function view(User $user, User $user)
{
    return $user->id === $user->id
}
```

ここで、先ほどお話しした「`User $user, User $user`ってどういうこと？」について説明します。

この2つの`$user`にはちゃんと違いがあります。1つ目の`$user`にはこのviewメソッドにて取得されたユーザーの情報が入っていて、2つ目の`$user`にはコントローラーのshowアクションから送られてきたユーザーの情報が入っています。

このままだと分かりづらいので、2つ目の方を`$request_user`に変更しておきましょう。

```php:UserPolicy.php（変更後）
public function view(User $user, User $request_user)
{
    return $user->id === $request_user->id;
}
```

## 完成！

これで機能としては完成しましたが、処理の流れを簡単に説明したいと思います！

IDが1のユーザーがログインしている状態と仮定して、
`https://○○○○/user/1`
というURLを
`https://○○○○/user/2`
に変更してリロードした際の処理を説明します。

####処理の流れ

1. コントローラーのshowアクションで、UserモデルからユーザーID2の情報を`$user`として取得。
1. `$this->authorize('view', $user);` で、ユーザーID2の情報を`UserPolicy.php`に渡す。
1. `UserPolicy.php`にて、Userモデルから本来のユーザーID1の情報を`$user`として取得、`$request_user`にはコントローラーから送られた`$user`（今回はユーザーID2の情報）の値が入っている。
1. `return 1 === 2;`となるのでfalseを返し、403エラーを表示する。

##最後に

以上、Laravelにおける認可機能の一部をご紹介しました！

初学者あるあるなのかなと思いますが、書いたコードをいざ言葉で説明するとなると、それが意外と難しいことに気づきました。

僕も初学者なので、初学者でも分かりやすい言葉で説明できるように意識したいと思います。

認識が間違っている点があれば、ぜひコメントをお願いいたします！！

## 参考文献

- [Laravel Document （Laravel 6.x 認可
）](https://reffect.co.jp/laravel/laravel-gate-policy-understand)
- [Laravel Gate(ゲート)、Policy(ポリシー)を完全理解](https://reffect.co.jp/laravel/laravel-gate-policy-understand)
- [【初心者向け】Laravel Policyの使い方(認可)](https://qiita.com/shonansurvivors/items/f8159e2c65af647cb45b)