Laravel5.1でMulti Auth（Kbwebs/MultiAuth編）


###追記

Laravel5.2に正式にMulti−Authに対応しました。なので、この記事はもう役割終了です。
[こちら](http://qiita.com/zaburo/private/cd3f2ccfb5c2d0fba340)をどうぞ。

---

私にとってLaravelの最大の難所は、Multi Authです。つまり、標準ではusersテーブルのみで行われる認証を、users（一般ユーザー）、admins（管理者）に分けた認証です。

極力自力作成派なので、

* [テーブルを分けないパターン](http://qiita.com/zaburo/items/2ebc725e29657206d7d9)（usersにroleを追加）
* [テーブルを分けるパターン](http://qiita.com/zaburo/items/5536bff2435b55c758f9)

を過去にトライしてみたのですが、毎日の実装の中で気楽に利用できるレベルではないと感じました。そこで、頼りたいのが、サードパーティー製のパッケージ？なのですが、なかなかこれというものがありません。

##Multi Authパッケージの現状（2015年7月27日時点）

私の知る限り、4.2までは、ollieread/multiauthというのが主流だったようなのですが、5.xに対応しておらず、いくつかのフォークが存在するも、password_resetは動かないとか、いろいろと制限があるようです。

* https://github.com/sboo/multiauth
* https://github.com/hacklee/laravel5-multi-auth

そのような中、5.1対応をうたう真新しいパッケージを発見！

* https://github.com/Kbwebs/MultiAuth

パスワードリセットへの対応もしているようです。

Authを中心に動かしてみたのでやりかたをメモしておきます。

##Kbwebs/MultiAuthを試す：準備

流れとしては次のような感じ

* Kbwebs/MultiAuthを設置
* 一般ユーザー用のテーブルとしてはusersテーブル（既存migrationファイル）を使う。
* モデルとしてはUserを使う。
* 管理者用テーブルとしてadminsテーブルを作る。但し、内容はusersと同じ。
* モデルとしてはAdminを使う（新規作成）。
* Seederでusers, adminsにユーザーを追加
* 各種認証を試す。

###Kbwebs/MultiAuthを設置する
基本は、[本家のサイト](https://github.com/Kbwebs/MultiAuth)のインストール手順通りに行う。

####ファイルの取得

composer.jsonを編集。

```php
"require": {
    "kbwebs/multiauth": "dev-master"
}
```
update。

```bash
composer update
```

####使えるようにする（Auth機能）

config/app.phpのAuthServiceProviderの差し替える。

>Illuminate\Auth\AuthServiceProvider::class
↓
Kbwebs\MultiAuth\AuthServiceProvider::class


####使えるようにする（PasswordReset機能：使う場合）

>Illuminate\Auth\Passwords\PasswordResetServiceProvider::class
↓
Kbwebs\MultiAuth\PasswordResets\PasswordResetServiceProvider::class

config/auth.phpの書き換え

```php
'multi-auth' => [
    'admin' => [
        'driver' => 'eloquent',
        'model'  => App\Admin::class
    ],
    'user' => [
        'driver' => 'eloquent',
        'model'  => App\User::class
    ]
]
```
注意点としては、App\Admin::classを''で囲わないこと。

eloquentではなく、databaseを利用する場合は、

```php
'multi-auth' => [
    'admin' => [
        'driver' => 'database',
        'table' => 'admins'
    ],
    'user' => [
        'driver' => 'database',
        'table' => 'users'
    ]
],
```
とする。

また、パスワードリセットを使う場合は、

```php
'password' => [
    'email' => 'emails.users.password',
    'table' => 'password_resets',
    'expire' => 60,
],
```

とする必要があるようだ(emailにusersを追加）。

###usersテーブルを作る

既にあるmigrateファイルをそのまま利用し、migrateする。

###Userモデルを作る

既にあるApp\Userを使う。

###adminsテーブルを作る

```bash
php artisan make:migration create_admins_table --create=admins
```
として、migrateのひな形を作り、users用のmigrateファイルを参考に、同じ構造のテーブルが生成されるようにする（もちろんテーブル名は違いますが）。

```php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAdminsTable extends Migration
{

    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password', 60);
            $table->rememberToken();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('admins');
    }
}
```

###Adminモデルを作る

モデルは、Userをリネームして、Admin.phpを生成し必要な箇所を変更するか、artisanコマンドでひな形を生成し、Userを参考に編集します。

```bash
php artisan make:model Admin
```
実際の内容は、下記の通りですが、PasswordReset系のuseをKbwebsのものに変更しています。
逆にUserの方も、PasswordResetは（必要なら）変更します。

```php
<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
//use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
//use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

use Kbwebs\MultiAuth\PasswordResets\CanResetPassword;
use Kbwebs\MultiAuth\PasswordResets\Contracts\CanResetPassword as CanResetPasswordContract;



class Admin extends Model implements AuthenticatableContract, CanResetPasswordContract
{
    use Authenticatable,CanResetPassword;

    protected $table = 'admins';
    protected $fillable = ['name', 'email', 'password'];
    protected $hidden = ['password', 'remember_token'];
}
```
元々のパスワードリセット系はコメントアウトし、Kbwebsのものを追加。
クラス名と、テーブル名はもちろん適切に変更する。
なお、上記コードは余計な？コメントを消去しています。

###Seederでユーザーを登録

テスト用にユーザーを登録します。ここではSeederを使ってみます。
Laravel5.1から、Seederのひな形も作れるようになりました。

```bash
php artisan make:seeder UserTableSeeder
php artisan make:seeder AdminTableSeeder
```
UserTableSeederはこんな感じ。

```php
<?php

use Illuminate\Database\Seeder;
use App\User;

class UserTableSeeder extends Seeder
{

    public function run()
    {
        //delete
        User::truncate();

        //Insert
        User::insert([
        	[
	        	'name' => 'user1',
	        	'email' => 'user1@test.com',
	        	'password' => Hash::make('user1')
        	]
        ]);
    }
}
```
AdminTableSeederはこんな感じ。

```php
<?php

use Illuminate\Database\Seeder;
use App\Admin;

class AdminTableSeeder extends Seeder
{
    public function run()
    {
        //delete
        Admin::truncate();

        //Insert
        Admin::insert([
        	'name' => 'admin1',
        	'email' => 'admin1@test.com',
        	'password' => Hash::make('admin1')
        ]);
    }
}
```
artisan db:seedコマンドでの実行対象となるよう、DatabaseSeeder.phpを編集します。

```php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        Model::unguard();

        $this->call(UserTableSeeder::class);
        $this->call(AdminTableSeeder::class);

        Model::reguard();
    }
}
```
で、実行します。

```bash
php artisan db:seed
```
データが生成されます。※現状の記述ではModelに問題があり、エラーが発生します（調査中）。

##Kbwebs/MultiAuthを試す：利用

利用は、

```php
Auth::admin()->attempt(['email'=>'admin1@test.com','password'=>'admin1']);
Auth::user()->attempt(['email'=>'user1@test.com','password'=>'user1']);
```
という感じになります。

実際には、Middleware等を設定して利用することになるでしょう。

###Middlewareの設定例
ここでは、admin認証用とuser認証用のMiddlewareを作ってみたいと思います。まずは、ひな形。

```bash
php artisan make:middleware AdminAuthenticate
php artisan make:middleware UserAuthenticate
```
AdminAuthenticateは、

```php
<?php

namespace App\Http\Middleware;

use Closure;

class AdminAuthenticate
{
    public function handle($request, Closure $next)
    {
        if(\Auth::admin()->check())
        {
            //do nothing
        }
        else
        {
            return "go to admin login page.";
        }

        return $next($request);
    }
}
```
とりあえずこんな感じ。adminとしてログインしてればスルー。して無ければ、loginメッセージを出します（実際にはログインページへのリダイレクトとかになると思います）。

UserAuthenticateは、

```php
<?php

namespace App\Http\Middleware;

use Closure;

class UserAuthenticate
{
    public function handle($request, Closure $next)
    {
        if(\Auth::user()->check())
        {
            //do nothing
        }
        else
        {
            return "go to user login page.";
        }

        return $next($request);
    }
}
```
基本、admin認証と同じ理屈。
Middlewareをルートで利用するためには、Kernel.phpに登録します（以下、抜粋）。

```php
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'admin' => \App\Http\Middleware\AdminAuthenticate::class,
    'user' => \App\Http\Middleware\UserAuthenticate::class,
];
```

###利用してみる
ここでは、route.phpだけの記述でlogin,認証,logout等が正しく動くがチェックしてみます。

```php
//admin

Route::get('admin/login',function(){
	Auth::admin()->attempt(['email'=>'admin1@test.com','password'=>'admin1']);
	return Auth::admin()->get()->email;
});

Route::get('admin/logout',function(){
	Auth::admin()->logout();
	return "admin logout";
});

Route::get('admin',['middleware'=>'admin',function(){
	return "admin page";
}]);

//user

Route::get('user/login',function(){
	Auth::user()->attempt(['email'=>'user1@test.com','password'=>'user1']);
	return Auth::user()->get()->email;
});

Route::get('user/logout',function(){
	Auth::user()->logout();
	return "user logout";
});

Route::get('user',['middleware'=>'user',function(){
	return "user page";
}]);

```

なお、prefixを使って、下記の様に書くこともできる。

```php
Route::group(['prefix'=>'admin','middleware'=>'admin'],function(){

    Route::get('/',function(){
        return "admin page";
    });
    
    Route::get('login',function(){
        Auth::admin()->attempt(['email'=>'admin1@test.com','password'=>'admin1']);
    	return Auth::admin()->get()->email;
    });
    
});
```

上記をroute.phpに設定したら、下記テストを行います。

* adminページにアクセスしてみる。login促しメッセーがでれば、正しく「拒否されている」。
* admin/loginにアクセスし、ログイン。ユーザー名が正しく取得できていればログイン完了。
* 再びadminにアクセスし、"admin page"が表示されていれば、認証されている。
* admin/logoutにアクセスし、ログアウトする。
* 再びadminにアクセスし、login促しメッセージが表示されればOK。

* userにおいても、上記と同じテストを行う。
* userにログイン状態で、adminにアクセスするとどうなるか試してみる。
* adminにログイン状態でuserにアクセスするとどうなるか試してみる。

####注意

adminとuserのログインは同時に行うことが可能なので、テストの際は、気をつける。

####パスワードリセット

私は、標準のパスワードリセットをあまり使わないので、いろいろ端折りましたが、本家サイトにパスワードリセットを使う場合の注意書きがまとまっています。

主な留意点としては、

* Modelにkbwebsの機能をuseする。
* config/auth.phpで'email' => 'emails.users.password'とする。
* パスワードリセット用テーブルは独自のものを生成する。

専用のリセットテーブルは、下記のコマンドで、

```php
php artisan kbwebs:multi-auth:create-resets-table
php artisan kbwebs:multi-auth:clear-resets
```
マイグレーションファイルの生成、全パスワードリセットができるみたいです。なお、Kbwebs独自のartisanコマンドを利用するには、ProviderにKbwebs\MultiAuth\PasswordResets\PasswordResetServiceProvider::classの追加が必要です。