Laravel5.2でトークン認証をカスタマイズしてみる

Laravel5.2でトークン認証middlewareの先を拡張してみたのでメモ。

# 環境
PHP 5.6.23 (cli) (built: Jun 22 2016 08:56:52) 
Laravel  5.2.43

# やりたいこと

APIの、トークン認証を実装したい。

1. トークンのキー名を変えたい（＆他判定処理追加）。
2. 検索テーブルの条件の複雑化

※認証であって、ログインではないので注意！
※**API認証をいかに安全に実装するか？論はかいてませんのであしからず。**

middlewareでの認証の流れは、まとめてくださってる方がいらっしゃるのでそちらをご参考に。
（自分もソース読んだけどね・・・）
[Laravelのmiddleware authを理解したい](http://qiita.com/washio12/items/59f5cde23b4205973c6b)

他、[公式](https://readouble.com/laravel/5.2/ja/authentication.html#adding-custom-guards)

# 実装方法

トークン認証を拡張したかったので、デフォルトのクラスをextendsしていく方向で。
実際の認証として、
Illuminate\GuardHelpers#guest() 
で呼ばれた先を変更してきます。

## middleware

最初から用意されているものをそのまま。返却値は適宜。

```\App\Http\Middleware\Authenticate.php
<?php

namespace Myapp\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class Authenticate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {

        if (Auth::guard($guard)->guest()) {
            // 独自になにがしか返却でもー
            return response('Unauthorized.', 401);
        }

        return $next($request);
    }
}
```

Auth::guard($guard)->guest()　で、
上記の、Illuminate\GuardHelpers#guest() 
が呼ばれてるので具体的なソースはそこを見ましょう。

## 独自のGuardクラスを実装

トークン認証を拡張したかったので、Illuminate\Auth\TokenGuardをextendして独自クラス作成。
Illuminate\Auth\TokenGuard#user() をオーバーライド。

```MyTokenGuard.php
<?php

namespace Myapp\Services\Auth;


use Illuminate\Auth\TokenGuard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Http\Request;

class MyTokenGuard extends TokenGuard {

    /**
     * Create a new authentication guard.
     *
     * @param  \Illuminate\Contracts\Auth\UserProvider  $provider
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    public function __construct(UserProvider $provider, Request $request)
    {
        $this->request = $request;
        $this->provider = $provider;
        $this->inputKey = 'atoken';
        $this->storageKey = 'api_token';
    }

    /**
     * Get the token for the current request.
     *
     * @return string
     */
    protected function getTokenForRequest()
    {
        $token = $this->request->input($this->inputKey);

        if (empty($token)) {
            $token = $this->request->bearerToken();
        }

        // 独自に他のトークンをとって突きあわせ処理

        return $token; //DBと突き合わせるトークン
    }
}
```

これで、リクエストパラメータ"atoken"を取得するようになる。もちろん、ヘッダーの別のキーも取れる。

## トークン用カラムを追加。

トークンを保存しておきたいテーブルにカラム追加しときましょう。

```migrateクラス#up()
Schema::connection('mycon')->table('t_user', function (Blueprint $table) {
    $table->string('api_token', 60)->unique();
});
```

この時のカラム名＝GuardクラスのstorageKeyの名前に。


## 独自のProviderクラスを実装

Eloquantを利用していてDBデータと突きあわせたかったので、Illuminate\Auth\EloquentUserProviderをextendsして独自クラスを作成。

```MyTokenProvider.php
<?php

namespace Myapp\Services\Auth;

use Illuminate\Auth\EloquentUserProvider;

class MyTokenProvider extends EloquentUserProvider  {


    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials)) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->createModel()->newQuery();

        // 独自の条件をつける

        return $query->first();
    }
}
```

これで、MyTokenGuard#getTokenForRequest() で返却した値をキーにしてさらに条件追加して検索できますぜ。


## AuthServiceProvider

Guardクラス、Providerクラスを登録します。

```App\ProvidersAuthServiceProvider.php

<?php

namespace Myapp\Providers;

use Myapp\Services\Auth\MyTokenProvider;
use Myapp\Services\Auth\MyTokenGuard;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Auth;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'Myapp\Model' => 'Myapp\Policies\ModelPolicy',
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);

        $this->app['auth']->extend('my_token', function($app, $name, array $config) {
            return new MyTokenGuard(Auth::createUserProvider($config['provider']), $app['request']);
        });

        $this->app['auth']->provider('my_api_token', function($app, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\UserProvider...
            return new MyTokenProvider($app['hash'], $config['model']);
        });
    }
}
```

'my_token','my_api_token' =  あとでconfigに設定するキーです

## config


ServiceProviderで記述したキーを、設定します。
※passwordsはいじってないので割愛。

```config\auth.php
<?php

return [

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

    'guards' => [
        'api' => [
            'driver' => 'my_token',
            'provider' => 'my_api',
        ],
    ],


    'providers' => [
        'my_api'  => [
            'driver' => 'my_api_token',
            'model' => Myapp\THoge::class
        ],
    ],

  
    'passwords' => [
      （略）
    ],

];

```


とりあえずここまで。
あとはPHPUnitとかでテストするべし。

(ちなみに、Guardクラスで、`use Illuminate\Http\Request;`を書き忘れてしばらくハマったんだなあ。。。)



# 参考
http://qiita.com/washio12/items/59f5cde23b4205973c6b
https://readouble.com/laravel/5.2/ja/authentication.html#adding-custom-guards