【Laravel】 認証や認可に関する補足資料

# はじめに

いつもどおり，以下のマニュアルの一通りの確認を先に。

- [認証 5.7 Laravel](https://readouble.com/laravel/5.7/ja/authentication.html)
- [認可 5.7 Laravel](https://readouble.com/laravel/5.7/ja/authorization.html)

# 「認証」と「認可」の違い

これを分かっていないと話にならないので念の為記載。

## 概念の違い

| 名称 | 説明 |
|:---:|:---|
| 認証<br>Authentication | <ul><li>本人であることを証明すること<li>1人1人を識別すること</ul> |
| 認可<br>Authorization | <ul><li>アクションを行うための権限を与えること<li>アクションを行う権限があるか確認すること</ul>　|

## ステータスコードや例外クラス名の違い

| 名称 | HTTP<br>Status | Application Exception | HTTP Exception |
|:---:|:---:|:----:|:----:|
| 認証<br>Authentication | 401<br>Unauthorized | [`AuthenticationException`] | [`UnauthorizedHttpException`]
| 認可<br>Authorization | 403<br>Forbidden | [`AuthorizationException`] | [`AccessDeniedHttpException`]

- アプリケーション例外は Laravel フレームワークで定義されているもので，HTTPに直接関係ない抽象的な部分ではまずこれをスローすべき。
- HTTP 例外は Symfony フレームワークで定義されているもので，**[`Handler::render()` でアプリケーション例外から変換して生成する](https://github.com/laravel/framework/blob/2b0f2e922d6c36c992f329ba09a8d909b1411cb5/src/Illuminate/Foundation/Exceptions/Handler.php#L161-L208)**のが一般的。

`401 Unauthenticated` のほうがどう考えても正しいが，そうなってないのには歴史的な理由があるのだろうか…？

[`AuthenticationException`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthenticationException.php
[`AuthorizationException`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/AuthorizationException.php
[`UnauthorizedHttpException`]: https://github.com/symfony/symfony/blob/db6784bb095de2c6ef0ab424efd5b06e71e3feef/src/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php
[`AccessDeniedHttpException`]: https://github.com/symfony/symfony/blob/db6784bb095de2c6ef0ab424efd5b06e71e3feef/src/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php

# 認証のアーキテクチャ

- `Auth` ファサードあるいはコンテナから `"auth"` のエイリアス名でアクセスできる，認証サービスのルートオブジェクトが [`AuthManager`] である。ここが認証サービスの中核となる。
- Guard が認証手段ごとの実装として存在し，ロジックの多くはここに集約される。 このクラスは **[`Guard` 契約]** を実装しなければならない。
- ユーザを取得してくる部分だけが UserProvider に抽象化して分離される。このクラスは **[`UserProvider` 契約]** を実装しなければならない。
- ユーザとして用いるクラスは **[`Authenticatable` 契約]** を実装しなければならない。
 
**<ins>認証済みのユーザを取得する場合</ins>**<br>

デフォルトの認証手段である [`SessionGuard`] を利用する場合を例として示す。

![session-user](https://qiita-image-store.s3.amazonaws.com/0/25060/4085ad6b-b120-c245-4231-d939ca94dd0b.png)

**<ins>新しくユーザを認証する場合</ins>**<br>

デフォルトの認証手段である [`SessionGuard`] を利用する場合を例として示す。

![session-attempt.png](https://qiita-image-store.s3.amazonaws.com/0/25060/e719f8d1-af94-0d25-b771-a4e10318a52e.png)

<small>※ 本当は `3. validate()` と書きたかったのだが，コード上何故かこのメソッドが使用されていなかったので避けた</small>

## [`AuthManager`]

ではソースコードを確認し，どんなメソッドがあるか確認してみよう。

### [`AuthManager`] の概要

まず，全体像を掴むためのメソッドを列挙する。

```php
class AuthManager implements FactoryContract
{
    /* ... */

    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    /* ... */

    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
}
```

以下の2点は必ず押さえておきたい。

- **[`AuthManager::guard()`] メソッドは Guard のインスタンスを返す。**
  1. 引数として渡されたガード名，もしくは `getDefaultDriver()` メソッドでデフォルトのガード名を取得
  2. 生成済み Guard のインスタンスキャッシュがあればそれを返し，無ければ `resolve()` メソッドで生成してキャッシュしてから返す
- **[`AuthManager::__call()`] メソッドによって呼び出しの多くは Guard に委譲される。**

### Guard の解決方法

では，具体的に Guard はどのように解決されるのだろうか？

```php
class AuthManager implements FactoryContract
{

    /* ... */

    /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        /* ... */
    }

    /**
     * Create a token based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\TokenGuard
     */
    public function createTokenDriver($name, $config)
    {
        /* ... */
    }

    /**
     * Get the guard configuration.
     *
     * @param  string  $name
     * @return array
     */
    protected function getConfig($name)
    {
        return $this->app['config']["auth.guards.{$name}"];
    }

    /**
     * Get the default authentication driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

    /* ... */

    /**
     * Register a custom driver creator Closure.
     *
     * @param  string  $driver
     * @param  \Closure  $callback
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }
}
```

Laravel らしい，マジックメソッドを使ったコードになっている。

- `getDefaultDriver()` メソッドは `"auth.defaults.guard"` 設定の値を返す。
  - この値はデフォルトでは `"web"` になっている
- `getConfig()['driver']` は `"auth.ガード名.driver"` 設定の値を返す。
  - この値はデフォルトでは `"session"`　になっている
- `resolve()` メソッドは `createXXXDriver()` という名前のメソッドを検索し，あればそれを実行する。
  - **デフォルトでは `createSessionDriver()` メソッドが呼ばれ， [`SessionGuard`] が生成される**
- **`resolve()` メソッドは [`Auth::extend()`] メソッドの呼び出しで登録されたカスタムガード生成関数がある場合，それを実行する。**

カスタムガード登録の例（公式マニュアルより）↓

>```php
>class AuthServiceProvider extends ServiceProvider
>{
>    /**
>     * サービスの初期起動後の登録実行
>     *
>     * @return void
>     */
>    public function boot()
>    {
>        $this->registerPolicies();
>
>        Auth::extend('jwt', function($app, $name, array $config) {
>            // Illuminate\Contracts\Auth\Guardのインスタンスを返す
>
>            return new JwtGuard(Auth::createUserProvider($config['provider']));
>        });
>    }
>}
>```

### UserProvider の解決方法

Guard に続いて， UserProvider の解決方法も見てみよう。大部分は [`CreatesUserProviders` トレイト]に委譲されているが，ほとんど Guard と同じロジックであるため詳細は省略する。

```php
class AuthManager implements FactoryContract
{
    use CreatesUserProviders;

    /* ... */

    /**
     * Register a custom provider creator Closure.
     *
     * @param  string  $name
     * @param  \Closure  $callback
     * @return $this
     */
    public function provider($name, Closure $callback)
    {
        $this->customProviderCreators[$name] = $callback;

        return $this;
    }

    /* ... */
}
```

デフォルトでは [`EloquentUserProvider`] が選択されるようになっており， [`Auth::provider()`] でカスタマイズできることが分かる。

次章からは，実装まで見ると長くなるため，契約を中心として見ていくことにする。要するに，使い方だけ覚えられれば問題ない，という考え方だ。

## Guard

[`Guard` 契約] と [`StatefulGuard` 契約] およびその一部のデフォルト実装である [`GuardHelpers`](https://github.com/laravel/framework/blob/26d3553bf87e18e780b5b8833bf0f35262ccc44f/src/Illuminate/Auth/GuardHelpers.php) を確認する。

以下は [`Guard` 契約] として定義されているメソッドだ。これらのメソッドは必ずすべての Guard が実装しなければならない。重要な箇所を太字にして示す。

| メソッド | 説明 |
|:---|:---|
| **`user`** | **セッションID・アクセストークンなど**をもとに<br>認証済みユーザを検証し， Authenticatable または `null` を返す。<br>**Authenticatable はキャッシュされる。**　|
| **`validate`** | **Eメールアドレスやパスワードなど**のクレデンシャル情報を検証して結果を論理値で返す。<br>これを実行するだけではログインしたことにならない。 |
| `check` | `user()` の返り値を論理値に変換して返す。 |
| `guest` | `check()` の返り値を否定して返す。 |
| `id` | `user()` の返り値から取り出したIDまたは `null` を返す。 |

以下は契約としては定義されていないが，ヘルパーに存在することから暗黙的に必須とされている場合があるメソッドだ。これらはフレームワークのバージョンアップで契約に追加される可能性もある。以下に一部を省略して示す。

| メソッド | 説明 |
|:---|:---|
| **`hasUser`** | **`Authenticatable` がキャッシュされた状態かどうかを確認する。**<br>これを実行しても認証処理は発生しない。 |
| `setUser` | 指定した `Authenticatable` をキャッシュする。 |
| `authenticate` | `validate()` と同等だが，検証失敗時に [`AuthenticationException`] をスローする。　|

`hasUser()` は副作用を一切発生させずに，キャッシュが存在する場合のみ Authenticatable を取得したい場合に非常に便利だ。 Eloquent Model 定義など，例外や余計なHTTP通信が絶対に起こってはいけない場所で重宝する。例えば管理者にだけ見える属性を定義したい場合，以下のような書き方をするとよいだろう。

```php:認証済みユーザが管理者である場合のみ&#32;super_secret_id&#32;を可視属性に加える
public function getVisible(): array
{
    return Auth::hasUser() && Auth::user()->isSuperAdmin()
        ? array_merge($this->visible, ['super_secret_id'])
        : $this->visible;
}
```


以下は [`StatefulGuard` 契約] として定義されているメソッドだ。 [`SessionGuard`] などステートフルな Guard にはこれらの実装も必要とされる。

| メソッド | 説明 |
|:---|:---|
| `attempt` | `validate()` と同等だが，検証成功後に自動的に `login()` を実行する。　|
| `login` | 指定した Authenticatable をキャッシュする。<br>リクエストを超えてキャッシュは保持される。 |
| `logout` | Authenticatable のキャッシュをクリアする。 |
| `viaMember` | 自動ログイントークンを使ってログインしているかどうかを返す。 |
| `once` | `login()` を使わずに直接 Authenticatable をキャッシュする。<br>リクエストを超えてキャッシュは保持されない。|
| `loginUsingId` | 指定したIDで Authenticatable をフェッチして `login()` を実行する。 |

## `Authenticatable`

[`Authenticatable` 契約]を確認しよう。これが既定の `App\User` に [`Authenticatable` トレイト] を使ってデフォルトで実装されている。

| メソッド | 説明 |
|:---|:---|
| **`getAuthIdentifierName`** | `id` など，識別子の名前を返す。 |
| **`getAuthIdentifier`** | 識別子の値を返す。 |
| `getPassword` | パスワードハッシュの値を返す。 |
| `getRememberToken` | 自動ログイントークンの値を返す。 |
| `setRememberToken` | 自動ログイントークンをセットする。 |
| `getRememberTokenName` | `remember_token` など，自動ログイントークンの名前を返す。 |

厄介なのが， `StatefulGuard` を利用しなくても Authenticatable に自動ログイントークンの実装が強制されていることだ。認証をすべて外部サービスに委譲している場合などには余計なお節介だ。こういう場合，以下のようなトレイトを作って `App\User` にミックスインするといいだろう。

```php:app/Auth/StatelessAuthenticatable.php
<?php

declare(strict_types=1);

namespace App\Auth;

use Illuminate\Database\Eloquent\Model;

/**
 * Trait StatelessAuthenticatable
 *
 * @mixin Model
 */
trait StatelessAuthenticatable
{
    /**
     * Get the name of the unique identifier for the user.
     *
     * @return string
     */
    public function getAuthIdentifierName()
    {
        return $this->getKeyName();
    }

    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier()
    {
        return $this->{$this->getAuthIdentifierName()};
    }

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword()
    {
        throw new \BadMethodCallException('Unexpected method call');
    }

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken()
    {
        throw new \BadMethodCallException('Unexpected method call');
    }

    /**
     * Set the token value for the "remember me" session.
     *
     * @param string $value
     */
    public function setRememberToken($value)
    {
        throw new \BadMethodCallException('Unexpected method call');
    }

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName()
    {
        throw new \BadMethodCallException('Unexpected method call');
    }
}
```

## UserProvider

続いて，[`UserProvider` 契約]を確認しよう。

| メソッド | 説明 |
|:---|:---|
| **`retrieveById`** | `id` など，識別子を使って Authenticatable を取得する。 |
| **`retrieveByCredentials`** | Eメールアドレスなど，ログインフォームへの入力情報を使って Authenticatable を取得する |
| **`validateCredentials`** | パスワードなど， ログインフォームへの入力情報を取得した Authenticatable を使って検証する |
| `retrieveByToken` | 自動ログイントークンを使って Authenticatable を取得する。 |
| `updateRememberToken` | 自動ログイントークンを更新する。 |

これも上記同様，外部で発行されたトークンを用いて認証を委譲している場合などには不必要なメソッドが複数存在する。該当する場合は，独自の UserProvider で `null` をリターンするか `BadMethodCallException` をスローすると良いだろう。

## [`RequestGuard`] の利用

外部で発行されたトークンを用いて認証を委譲している場合などに対応するために，独自の Guard を作成することもできるが，ロジックがシンプルである場合には [`RequestGuard`] を利用するだけで解決するだろう。自作するかどうかは以下のように判断すると良い。

- カスタマイズする内容は `user()` の実装のみで，ガード自体にメソッドを追加したりする必要がない。ロジックも大量にメソッド分割しなければならないほど複雑ではない。
  - はい　→ [`RequestGuard`] で十分
  - いいえ → Guard の自作を検討

```php:UserProviderを無視する場合
Auth::viaRequest('custom-token', function (Request $request): ?User {
    return User::where('token', $request->token)->first();
});
```

```php:UserProviderの仕組みに乗る場合
Auth::viaRequest('custom-token', function (Request $request, UserProvider $provider): ?User {
    return $provider->retrieveByCredentials($request->only('token'));
});
```

なお，ライブラリとして Guard を実装する場合にはできるだけ UserProvider の仕組みに乗っかるべきだが，アプリケーションを作るだけなら過度な抽象化に付き合う必要はなく， UserProvider を使わない Guard が存在しても特に問題はない。

## [`Authenticate` ミドルウェア]

ここまで認証のための処理を見てきたが，リクエストを処理する際に認証処理を呼び出す起点になるのが [`Authenticate` ミドルウェア] である。あらかじめ定義された Guard が順番に認証を試み，成功した時点で Authenticatable を取得して処理を続行，という動きをする。以下の図のようなイメージだ。

![authenticate-middleware.png](https://qiita-image-store.s3.amazonaws.com/0/25060/25d2e89e-56e4-2374-19f3-4ce8541623d6.png)

では，この実装を読んでみよう。

```php
<?php

namespace Illuminate\Auth\Middleware;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;

class Authenticate
{
    /**
     * The authentication factory instance.
     *
     * @var \Illuminate\Contracts\Auth\Factory
     */
    protected $auth;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Auth\Factory  $auth
     * @return void
     */
    public function __construct(Auth $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string[]  ...$guards
     * @return mixed
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

    /**
     * Determine if the user is logged in to any of the given guards.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  array  $guards
     * @return void
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    protected function redirectTo($request)
    {
        //
    }
}
```

```php
class AuthManager implements FactoryContract
{
    /* ... */

    /**
     * Set the default guard driver the factory should serve.
     *
     * @param  string  $name
     * @return void
     */
    public function shouldUse($name)
    {
        $name = $name ?: $this->getDefaultDriver();

        $this->setDefaultDriver($name);

        $this->userResolver = function ($name = null) {
            return $this->guard($name)->user();
        };
    }

    /* ... */

}
```

コードを読むポイントは2つ。

- `$guards` には， [`App\Http\Kernel`] または [`App\Providers\RouteServiceProvider`] でルーティング定義の際に **`auth:token,session`** のような形式でミドルウェアを利用した場合， ガード名の配列が入ってくる。
  - 先頭の Guard から順番にチェックされ，認証が成功した時点で終了する。
  - 最後まで認証できなかった場合には [`AuthenticationException`] をスローする。
- `shouldUse()` メソッドでは，ユーザ取得に成功した場合のみ `$this->userResolver`　を上書きしている。
  - このクロージャは [`Gate::resolveUser()`] や [`Request::user()`] で使用されている。

コントローラメソッドにて，ファサード形式で

```php
public function something(Request $request)
{
    $user = Auth::user();
    /* ... */
}
```

なんて使い方をしたりするが，上記ゆえに実は引数の [`Request`] をそのまま流用して

```php
public function something(Request $request)
{
    $user = $request->user();
    /* ... */
}
```

なんて形でも取れたりする。
では，認証はこのぐらいにしておいて，認可に進もう。

# 認可のアーキテクチャ

認証したユーザに対し，具体的にどんな動作を許可できるかどうかを定義するのがこのレイヤーだ。

![authorization.png](https://qiita-image-store.s3.amazonaws.com/0/25060/24d3402d-76e0-4006-47c1-c73ce771a169.png)

[`Gate`] が認可サービスのルートオブジェクトになり，一連の処理の中核となる。同名のファサードに対応するのもこのクラスだ。そして，以下のような処理をすべて担う。

- 単一の権限判定関数として， Ability を **[`Gate::define()`]** で登録。
- Eloquent Model に対する複数の権限判定メソッドの集合であるクラスとして， Policy を **[`Gate::policy()`]** で登録。
- **[`Gate::authorize()`]** で権限名称から使用すべき関数・メソッドを特定して，認可処理を行い，失敗した場合には [`AuthorizationException`] をスローする。

## [`Gate`] の概要

[`Gate`] を自前で実装することはほぼ無いが，一応 [`Gate` 契約] は存在する。使い方を確認するにはちょうどいいので，まずこれを確認しよう。

**<ins>登録用メソッド</ins>**

| メソッド | 説明 |
|:---|:---|
| **`define`** | **コールバックを Ability として登録する。** |
| **`policy`** | **クラスを Policy として登録する。** |
| `before` | 認可処理の前処理を登録する。<br>`null` 以外の値を返すと認可処理をスキップできる。 |
| `after` | 認可処理の後処理を登録する。 |

[`App\Providers\AuthServiceProvider`] の `registerPolicies()` メソッドで自動的に [`Gate::policy()`] が呼ばれるため，明示的にこれを呼ぶことはあまり無いと思われる。前処理・後処理も特段込み入ったことをしない限りは必要ない。

**<ins>判定用メソッド</ins>**

| メソッド | 説明 |
|:---|:---|
| **`raw`** | **`before` → コールバック → `after` の順に実行する。** |
| **`authorize`** | `raw()` の返り値が<br><ul><li>[`Response`] → そのまま返す</li><li>truthy → メッセージが空の [`Response`] を返す</li><li>**falsy → [`AuthorizationException`] をスロー**</li></ul> |
| `allows` | `raw()` を論理値に変換して返す。<br>**[`AuthorizationException`] 安全**。|
| `denies` | `raw()` の否定を論理値にして返す。<br>**[`AuthorizationException`] 安全**。 |
| `check` | 複数の `raw()` の返り値がすべて truthy かどうかを返す。<br>**[`AuthorizationException`] 安全**。|
| `any` | 複数の `raw()` の返り値のいずれか1つ以上が truthy かどうかを返す。<br>**[`AuthorizationException`] 安全**。|

ここで「[`AuthorizationException`] 安全」と書いたものは， [`AuthorizationException`] が発生したとき，キャッチして `false` をリターンする。

基本的に最も頻繁に使用されるのは [`Gate::authorize()`] で，これは後述する [`Authorize` ミドルウェア] から呼ばれることになる。そのほか，局所的に [`Gate::allows()`] [`Gate::denies()`] などの出番もありそうだ。

**<ins>その他のメソッド</ins>**

| メソッド | 説明 |
|:---|:---|
| **`forUser`** | 認証ユーザを切り替える。 |
| `getPolicyFor` | Eloquent Model クラス名に対応する Policy クラスのインスタンスを返す。 |
| `has` | 指定した Ability が登録されているかを確認する。<br>Policy として登録されたものには反応しない。 |
| `abilities` | Ability 一覧を返す。<br>Policy として登録されたものには反応しない。 |

こちらも，バッチ処理などで局所的に [`Gate::forUser()`] を使うことはあるだろう。このメソッドは**[新しい `Gate` インスタンスを返す](https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L629-L645)**ため，グローバルを汚さない。

```php
/**
 * Get a gate instance for the given user.
 *
 * @param  \Illuminate\Contracts\Auth\Authenticatable|mixed  $user
 * @return static
 */
public function forUser($user)
{
    $callback = function () use ($user) {
        return $user;
    };

    return new static(
        $this->container, $callback, $this->abilities,
        $this->policies, $this->beforeCallbacks, $this->afterCallbacks
    );
}
```

## 認可メソッド実装のルール

### [`Response`] クラスとは？

さて，ここで唐突に [`Response`] というクラスが出てきた。

```php
<?php

namespace Illuminate\Auth\Access;

class Response
{
    /**
     * The response message.
     *
     * @var string|null
     */
    protected $message;

    /**
     * Create a new response.
     *
     * @param  string|null  $message
     * @return void
     */
    public function __construct($message = null)
    {
        $this->message = $message;
    }

    /**
     * Get the response message.
     *
     * @return string|null
     */
    public function message()
    {
        return $this->message;
    }

    /**
     * Get the string representation of the message.
     *
     * @return string
     */
    public function __toString()
    {
        return (string) $this->message();
    }
}
```

マニュアルに一切登場しないが，どうやらこのクラスは認可成功時にもメッセージを返せるようにするためのものらしい。あまり使う機会は無さそうだが，もし「認可された理由」をユーザに返却したい場合には採用してみるとよいだろう。

### 論理値を返すべきか，それとも例外を投げてもいいか

マニュアルの例を見ると，論理値を `true` `false` で返しているだけのものが目立つ。

>
>```php
>Gate::define('update-post', function ($user, $post) {
>    return $user->id == $post->user_id;
>});
>```
>
>
>```php
><?php
>
>namespace App\Policies;
>
>use App\User;
>use App\Post;
>
>class PostPolicy
>{
>    /**
>     * ユーザーにより指定されたポストが更新可能か決める
>     *
>     * @param  \App\User  $user
>     * @param  \App\Post  $post
>     * @return bool
>     */
>    public function update(User $user, Post $post)
>    {
>        return $user->id === $post->user_id;
>    }
>}
>```

著者自身も当初はこの実装が最も正しく美しいと考えていたが，

- [`Gate::authorize()`] は論理値だけでなく [`Response`] を返してもよい
- [`Gate::authorize()`] があっさりと [`AuthorizationException`] をスローしている
- [`Gate::allows()`] [`Gate::denies()`] などは [`AuthorizationException`] をキャッチして例外安全にしている

ということを踏まえると，極端な話こんな実装もアリといえばアリだろうか。

```php
<?php

namespace App\Policies;

use App\User;
use App\Post;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Access\Response;

class PostPolicy
{
   public function update(User $user, Post $post)
   {
       if ($user->isSuperAdmin()) {
           return new Response('本人じゃないけど管理者だから特別にOK');
       }
       if ($user->id !== $post->user_id) {
           throw new AuthorizationException('本人しか更新はできません');
       }
       return new Response('本人だから更新してOK');
   }
}
```

現実的に考えると，落とし所としてはこれぐらいが妥当であろう。

- 基本的には論理値を返す。
- **ユーザに対して認可させない理由を詳細に伝えたい場合， [`AuthorizationException`] をスローする。**

### 第1引数は必ず Authenticatable になる，但し…

以前，認証の Guard に関する説明で以下のように述べた。

> - `shouldUse()` メソッドでは，ユーザ取得に成功した場合のみ `$this->userResolver`　を上書きしている。
>   - このクロージャは **[`Gate::resolveUser()`]** や [`Request::user()`] で使用されている。

[`Gate`] は，このメソッドを使って Authenticatable を取得し，第1引数に渡している。**但し，場合によってはユーザ認証していないリクエストに対しても認可処理を行いたい**というケースがあるだろう。

素直に Ability や Policy を登録した場合，ゲストに対してはメソッドを一切実行せずに終わるが，例外がある。第1引数が以下を満たす場合だ。これは非常に複雑だが， [`Gate`] のソースを深く読んでいくと分かる。

```php
/**
 * Determine if the given parameter allows guests.
 *
 * @param  \ReflectionParameter  $parameter
 * @return bool
 */
protected function parameterAllowsGuests($parameter)
{
    return ($parameter->getClass() && $parameter->allowsNull()) ||
           ($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue()));
}
```

- **型としてクラスが指定されており，かつそれが `null` 許容されている**
- **デフォルト値があり，かつそれが `null` である**

つまり，以下のような場合だ。このいずれかの条件を満たす場合，認証していなくても認可を通すことが可能になる。

```php
public function action(?User $user, Post $post);
```

```php
public function action($user = null, $post = null);
```

実装側に制約が与えられる引数は第1引数のみである。第2引数以降は呼び出し側が与えるものになる。

## 認可メソッド呼び出しのルール

[`Gate::authorize()`] （および類似するメソッド） は， `$ability` `$arguments` という引数を取る。しかしこの部分だけを見ても，使い方がさっぱりわからない。 `$arguments` ってなんだよ。

```php
/**
 * Determine if the given ability should be granted for the current user.
 *
 * @param  string  $ability
 * @param  array|mixed  $arguments
 * @return \Illuminate\Auth\Access\Response
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function authorize($ability, $arguments = [])
{
    $result = $this->raw($ability, $arguments);

    if ($result instanceof Response) {
        return $result;
    }

    return $result ? $this->allow() : $this->deny();
}
```

コードを追いたいが，あまりにも複雑なので，要所だけ端折って掲載する。

### [`Gate::define()`] または [`Gate::resource()`] で登録した Ability を呼び出す場合

こちらはシンプルで， 定義した `$ability` の第2引数以降をそのまま `$arguments` として与えるだけでよい。

```php
Gate::define('update-post', function ($user, $post) {
    /* ... */
});
Gate::define('update-comment', function ($user, $post, $comment) {
    /* ... */
});
```

```php
Gate::authorize('update-post', [$post]);
Gate::authorize('update-comment', [$post, $comment]);
```

第2引数が配列ではない場合，自動的に配列でラップされるので，以下のように書くこともできる。**但し，第3引数以降は拾ってくれないことに注意。**

```php
Gate::authorize('update-post', $post);

// これはダメ！
// Gate::authorize('update-comment', $post, $comment);
```

### [`Gate::policy()`] で登録した Policy を呼び出す場合

こちらがやや複雑になる。

**<ins>一般形</ins>**

- 認可メソッド名をそのまま `$ability` とする。
  - **但し， *camelCase* 表現のポリシーメソッドは *kebab-case* で書いてもよい。**<br>Ability との一貫性の面からこちらのほうが推奨される。
- **認可メソッドの第2引数以降を格納した `$arguments` の先頭に， [`Gate::policy()`] の第1引数と同じ Eloquent Model クラス名を入れる。**

```php
class PostPolicy
{
    public function update(User $user, Post $post)
    {
        /* ... */
    }
    public function reportForSpam(User $user, Post $post)
    {
        /* ... */
    }
}
class CommentPolicy
{
    public function update(User $user, Post $comment, Comment $comment)
    {
        /* ... */
    }
}
```

```php
Gate::policy(App\Post::class, App\PostPolicy::class);
Gate::policy(App\Comment::class, App\CommentPolicy::class);
```

```php
Gate::authorize('update', [App\Post::class, $post]);
Gate::authorize('update', [App\Comment::class, $post, $comment]);
Gate::authorize('report-for-spam', [App\Post::class, $post]);
```

**<ins>第2引数の Eloquent Model に対応する Policy を呼び出す場合の省略形</ins>**

- **第2引数の Eloquent Model インスタンスと， [`Gate::policy()`] の第1引数として使った Eloquent Model クラス名が対応する場合， Policy クラス名を省略することができる。**

```php
class PostPolicy
{
    public function update(User $user, Post $post)
    {
        /* ... */
    }
}
```

```php
Gate::policy(App\Post::class, App\PostPolicy::class);
```

```php
// ↓すべて等価
Gate::authorize('update', $post);
Gate::authorize('update', [$post]);
Gate::authorize('update', [App\Post::class, $post]);
```

この省略形が Policy の理想とする美しい形だ。使える場合には積極的に使っていきたい。

### どうやって Ability と Policy を区別して判定しているのか

もはやゴリ押しと言えるような if 分岐による。最も重要な部分を日本語コメントつきで引用する。

```php
/**
 * Resolve the callable for the given ability and arguments.
 *
 * @param  \Illuminate\Contracts\Auth\Authenticatable|null  $user
 * @param  string  $ability
 * @param  array  $arguments
 * @return callable
 */
protected function resolveAuthCallback($user, $ability, array $arguments)
{
    // $arguments[0] がセットされていて，
    // $arguments[0] から Policy インスタンスが解決できて，
    // $ability の camelCase -> kebab-case 変換込みで対応するメソッドを見つけることができるか？
    if (isset($arguments[0]) &&
        ! is_null($policy = $this->getPolicyFor($arguments[0])) &&
        $callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
        // そのメソッドを実行するためのクロージャを返す
        // （ canBeCalledWithUser の判定などはこのクロージャの中でやる）
        return $callback;
    }

    // $ability に対応するコールバックが Gate::define() で登録され，
    // $user　が null の状態でゲストを受け入れない　Ability を実行してしまってエラーになる心配がないか？
    if (isset($this->abilities[$ability]) &&
        $this->canBeCalledWithUser($user, $this->abilities[$ability])) {
        // そのコールバックを返す
        return $this->abilities[$ability];
    }

    // 何もしないクロージャを返す
    return function () {
        return null;
    };
}
```

## [`Authorize` ミドルウェア]

認可処理も認証処理同様に，明示的に自分で記述せずにミドルウェアに任せることもできる。
（但し，可読性が著しく悪化する場合は推奨しない）

前項で説明した一般形・特殊形の上にさらにルートモデルバインディングを加味すると，以下のような書き方ができる。

**<ins>一般形</ins>**

- `can:update,App\Post,post`
- `can:update,App\Comment,post,comment`
- `can:report-for-spam,App\Post,post`

**<ins>第2引数の Eloquent Model に対応する Policy を呼び出す場合の省略形</ins>**

- `can:update,post`
- `can:report-for-spam,post`

[`Authorize` ミドルウェア]は以下ようなコードになっている。

```php
<?php

namespace Illuminate\Auth\Middleware;

use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Access\Gate;

class Authorize
{
    /**
     * The gate instance.
     *
     * @var \Illuminate\Contracts\Auth\Access\Gate
     */
    protected $gate;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function __construct(Gate $gate)
    {
        $this->gate = $gate;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $ability
     * @param  array|null  ...$models
     * @return mixed
     *
     * @throws \Illuminate\Auth\AuthenticationException
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function handle($request, Closure $next, $ability, ...$models)
    {
        $this->gate->authorize($ability, $this->getGateArguments($request, $models));

        return $next($request);
    }

    /**
     * Get the arguments parameter for the gate.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  array|null  $models
     * @return array|string|\Illuminate\Database\Eloquent\Model
     */
    protected function getGateArguments($request, $models)
    {
        if (is_null($models)) {
            return [];
        }

        return collect($models)->map(function ($model) use ($request) {
            return $model instanceof Model ? $model : $this->getModel($request, $model);
        })->all();
    }

    /**
     * Get the model to authorize.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  string  $model
     * @return \Illuminate\Database\Eloquent\Model|string
     */
    protected function getModel($request, $model)
    {
        return $this->isClassName($model) ? trim($model) : $request->route($model, $model);
    }

    /**
     * Checks if the given string looks like a fully qualified class name.
     *
     * @param  string  $value
     * @return bool
     */
    protected function isClassName($value)
    {
        return strpos($value, '\\') !== false;
    }
}
```

ミドルウェアの `handle()` メソッドは

```php
public function handle($request, Closure $next, ...$args);
```

とされることが一般的だが，ここでは `$args` の1つ目を取り出して

```php
public function handle($request, Closure $next, $ability, ...$models);
```

と分けていることに注意されたい。結果として，例えば `can:update,App\Comment,post,comment` なら

```php
$ability = 'update';
$models = [App\Comment::class, 'post', 'comment'];
```

という形になる。あとはルートモデルバインディングでインスタンスを取ってくるだけなのだが，「クラス名っぽい」文字列の場合トリミングしてそのままにしていることが分かる。これは Policy を一般系の形で利用したときのための対策だ。

そして最終的に

```php
$this->gate->authorize('update', [App\Comment::class, $post, $comment]);
```

となって [`Gate`] の処理が走り始める。

## Authorizable

最後に， [`Authorizable` 契約]および[`Authorizable` トレイト]によるデフォルト実装も確認しておこう。これらは必須ではないが， `App\User` から利用できるヘルパーメソッド的な役割を担っている。
（ヘルパー的役割であるがゆえに，このトレイトは Auth パッケージではなく Foundation パッケージのほうに入っている）

```php
<?php

namespace Illuminate\Foundation\Auth\Access;

use Illuminate\Contracts\Auth\Access\Gate;

trait Authorizable
{
    /**
     * Determine if the entity has a given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function can($ability, $arguments = [])
    {
        return app(Gate::class)->forUser($this)->check($ability, $arguments);
    }

    /**
     * Determine if the entity does not have a given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function cant($ability, $arguments = [])
    {
        return ! $this->can($ability, $arguments);
    }

    /**
     * Determine if the entity does not have a given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function cannot($ability, $arguments = [])
    {
        return $this->cant($ability, $arguments);
    }
}
```

[`Gate::forUser()`] が使用されているため，気兼ねなくユーザ単位で使用することができる。また [`Gate::check()`] が使用されているため例外安全でもある。どうしてもコントローラメソッド内で認可を行いたい場合には使っていきたい。

[`AuthManager`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php
[`AuthManager::guard()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php#L58-L69
[`AuthManager::__call()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php#L283-L293
[`AuthManager::extend()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php#L255-L267
[`CreatesUserProviders` トレイト]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/CreatesUserProviders.php
[`EloquentUserProvider`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/EloquentUserProvider.php
[`SessionGuard`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/SessionGuard.php
[`Guard` 契約]: https://github.com/illuminate/contracts/blob/006e9e0d9ba19d1ee14f05870c73300680b53daf/Auth/Guard.php
[`StatefulGuard` 契約]: https://github.com/illuminate/contracts/blob/006e9e0d9ba19d1ee14f05870c73300680b53daf/Auth/StatefulGuard.php
[`GuardHelpers`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/GuardHelpers.php
[`RequestGuard`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/RequestGuard.php
[`Request`]: https://github.com/illuminate/http/blob/c7ef1ffda62dfff523a2773dae201d331fe5bacd/Request.php
[`Authenticatable` 契約]: https://github.com/illuminate/contracts/blob/10ef21dada35a9e1860827d1ff01b469c6da9af7/Auth/Authenticatable.php
[`Authenticatable` トレイト]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Authenticatable.php
[`Authorizable` 契約]: https://github.com/illuminate/contracts/blob/10ef21dada35a9e1860827d1ff01b469c6da9af7/Auth/Access/Authorizable.php
[`Authorizable` トレイト]: https://github.com/laravel/framework/blob/0ce78ece7cd7d969646112b9377994db0318e396/src/Illuminate/Foundation/Auth/Access/Authorizable.php
[`UserProvider` 契約]: https://github.com/illuminate/contracts/blob/d441b3099ceb5ad2dc41a40d23fabca71647ab04/Auth/UserProvider.php
[`Auth::user()`]: https://github.com/illuminate/contracts/blob/d441b3099ceb5ad2dc41a40d23fabca71647ab04/Auth/Guard.php#L21-L26
[`Auth::attempt()`]: https://github.com/illuminate/contracts/blob/d441b3099ceb5ad2dc41a40d23fabca71647ab04/Auth/StatefulGuard.php#L7-L14
[`Auth::extend()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php#L255-L267
[`Auth::provider()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthManager.php#L269-L281
[`AuthenticationException`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/AuthenticationException.php
[`Authenticate` ミドルウェア]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Middleware/Authenticate.php
[`Authorize` ミドルウェア]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Middleware/Authorize.php
[`App\Http\Kernel`]: https://github.com/laravel/laravel/blob/915667a8d5fa31e7d35b617f64c47ab67a64a171/app/Http/Kernel.php
[`App\Providers\RouteServiceProvider`]: https://github.com/laravel/laravel/blob/915667a8d5fa31e7d35b617f64c47ab67a64a171/app/Providers/RouteServiceProvider.php
[`App\Providers\AuthServiceProvider`]: https://github.com/laravel/laravel/blob/915667a8d5fa31e7d35b617f64c47ab67a64a171/app/Providers/AuthServiceProvider.php
[`Gate`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L3
[`Gate` 契約]: https://github.com/illuminate/contracts/blob/d441b3099ceb5ad2dc41a40d23fabca71647ab04/Auth/Access/Gate.php
[`Gate::getPolicyFor()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L497-L522
[`Gate::authorize()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L279-L297
[`Gate::define()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L101-L121
[`Gate::resource()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L123-L145
[`Gate::policy()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L183-L195
[`Gate::allows()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L223-L233
[`Gate::check()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L247-L263
[`Gate::forUser()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L629-L645
[`Gate::denies()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L235-L245
[`Gate::resolveUser()`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Gate.php#L647-L655
[`Request::user()`]: https://github.com/illuminate/http/blob/c7ef1ffda62dfff523a2773dae201d331fe5bacd/Request.php#L511-L520
[`$request->user()`]: https://github.com/illuminate/http/blob/c7ef1ffda62dfff523a2773dae201d331fe5bacd/Request.php#L511-L520
[`Response`]: https://github.com/illuminate/auth/blob/0d1a6bd0a7118c71a5a4efaf7530f8957e2e1d12/Access/Response.php