やはりお前らのMulti-Auth は間違っている

……と言いたかったけどどうやら5.3で少し変わってたからみたい。

こんにちは、Laravel初心者のHinaloeです。
[ブログ](https://blog.hinaloe.net/2016/09/15/try-passport-laravel-5-3/)や[Qiita](http://qiita.com/kimama1997/items/25a08122418df782d2b9)にPassportの記事を書いてたら、初心者なのに今月末に[Laravel.osaka 2016](https://php-jp.github.io/laravel-osaka-2016/)ってイベントでPassportの話をすることになりました。よろしくお願いします。

## Laravelユーザーの皆さん、Multi-Auth、使ってますよね？

[Laravel 5.2 でやっとこさ標準で使えるようになった](http://qiita.com/zaburo/items/cd3f2ccfb5c2d0fba340)んです。使わない手は無いですよね。

意識していなくても使っているかもしれません。

5.2以降ではデフォルトで`web`と`api`の2つのAuthentication Guardsが定義されてい、それぞれ目的に応じて使い分けているかと思いますがこれもMulti Authです。デフォルトで使えるドライバは`session`,`token`ですが、たとえばPassportもこれに追加されます。

で、

なんといってもMulti Authの醍醐味は、 **これら複数の認証方法を横断して認可することが可能であること**ですよね。

たとえば以下のように `web`, `admin`, `api` の３つのguardsを用意した場合。

```php:config/auth.php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'admin',
        ],
        'admin' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
        'admin' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
    ],

```

これらの内コントローラやルーティング、アクション毎に必要に応じてアクセス許可を設定することが簡単にできます。

たとえば

- `web`のみ
- `web`と`api`
- `web`と`admin`
- `web` と `admin` と `api`
- これらの何れも＆非ログインも含む
- いずれでもログインしていない (`guest`)

などなど。

そもそも`web` ミドルウェアグループと `api` ミドルウェアグループでは大幅に当てているミドルウェアが異なるのでこれらをわざわざ跨がせはしないかもしれませんが、複数のアカウント種別で同一エンドポイントが利用できるというのは重要な事です。
(以降、上で提示した3つのguardを使って説明します)

## 複数のguardってどうやって受け入れるんだっけ？

### 単数のguardなら……
単数のguardを受け入れる場合は言うまでもないですよね。
RouterやControllerでmiddlewareに`'auth:admin'`や`'auth:api'`のような値を書いておけばそのルートは指定したメソッドでの認証が必要になり、認証が通らなければ401が投げられ(あるいはリダイレクトされたり)します。まあ当然ですよね。

### じゃあuser+adminだと……
まず愚直に考えそうなのはミドルウェアを複数回定義することです。(自分はそうでした)

```php
[
	'middleware' =>  [
		'auth:web',
		'auth:admin',
	]
]
```

なんだかいけそうな気がする〜〜〜〜〜けど案の定これは通りません。(それもそう)

### ところでMulti Authについて書いている記事を読んでみると……

多くの記事では`Auth::guard('admin')->user()`とか`Auth::guard('admin')->check()`のように **アクション毎に**書いているでは無いですか。それもよく読んでみると、`Auth::user()`じゃダメだから`Auth::guard('admin')->user()`使ってね、とまであるじゃないですか。


<p style="font-size:2em">面倒すぎるだろそれ<p>

どう考えても`Auth::user()`が使えないというのは面倒。そもそも`$request->user()`だって使えないんでしょこれ。

とりあえずひとまず解決には至らなかったので自分は

```php
        $user = Auth::guard('web')->user()??Auth::guard('admin')->user()??false;
        if (!$user) {
            throw new AuthenticationException();
        }
```

のように書いて一時凌ぎをしていました。
が、これアクション毎に書いていられるわけ無いですよね？w

### Laravel 5.3 ではこの心配は必要ないんです

実はLaravelの `Authenticate` ミドルウェア、5.2と5.3で配置場所がぜんぜん違うことに気づけば答えは出たも同然でした。

このミドルウェア、Laravel5.2では`App\Http\Middleware`にあったものが5.3より`Illuminate\Auth\Middleware`とコアに移動し、中身が少し変わってます。

特に特徴的なのがhundleの引数。PHP5.6+でサポートされている可変長引数を利用して **ミドルウェアに複数のguard**を渡せるようになりました。

つまり、`'auth:web,admin'`のようにルーティングやコントローラで指定してやれば、web, admin 何れの方法でもアクセス可能になるってわけです。(複数の認証を満たせるリクエストならばここで一番前に書いたものが使用されるはず)

渡したguardsリストの内、リクエストに対して有効な物があればそれが`$this->auth->shouldUse($guard);`されるので各アクションで`Auth::user()`のようなメソッドをそのまま使用できます。

(どの方法を使用しているかdetectするには`Auth::guard($guard)->check()`使うしか無いのかな……前述の ___複数満たせる場合___を考慮する必要がありそうだけど。)

### 実はこれ、Laravel 5.2 に適用できるんです
出来る出来るって言いながら検証したわけではないし妄想。

Laravel 5.2 にも Laravel5.3の上記同様のことが可能なメソッドは揃っているので、Authenticateミドルウェア(`App\Http\Middleware\ Authenticate.php)の中身をLaravel 5.3のそれ(`\Illuminate\Auth\Middleware\Authenticate`)で上書きすれば闇から解放される、はず。
なお要PHP5.6+

## 〆
Multi-Auth、何気につい最近までなかったのが謎なくらい需要あるので活用していきましょう。

[Laravel.osaka 2016](https://php-jp.github.io/laravel-osaka-2016/) への参加もお待ちしています！

✧Laravelこわいみなさんはこのあたり使いこなしてると思うので🔪指摘大歓迎です✧