Laravel 5.4 のログイン認証で複数テーブルを使う

果たしてこれでいいのかという疑問と、作業ログを兼ねて。Laravel をよくわかっていないので、変なことしてるかも。指摘お待ちしています。

## 実現したいこと

* 1つのログイン画面で、複数テーブルを使った認証を行う
  + `User` テーブルと, `Admin` テーブルがあって、どちらかにあればログインできて、それぞれの画面に遷移する、みたいな。
  + その上 `User` テーブルと `Admin` テーブルで認証に使用するカラムが異なる
    - `User` テーブルが `id`
    - `Admin` テーブルが `username`

標準の `make:auth` は使えないのかなーと思っているので、自分で作っていく。

## 環境

* macOS 10.12.3
* PHP 7.0
* Laravel 5.4
* MySQL

## 準備

### プロジェクトの作成といろいろ生成

```bash:bash
$ composer create-project laravel/laravel multi-auth-sample --prefer-dist "5.4.*"
$ cd multi-auth-sample
$ php artisan make:controller LoginController
$ php artisan make:controller AdminController
$ php artisan make:controller UserController
$ php artisan make:model Admin -m # User は既存のものを使う
$ php artisan make:seeder AdminTableSeeder
$ php artisan make:seeder UserTableSeeder
$ php artisan make:middleware CheckAuthenticate
$ php artisan make:middleware CheckAdminAuthenticate
```

### route ファイル編集

```php:routes/web.php
<?php
// ログイン画面
Route::get('/', 'LoginController@index')->name('login.index');
Route::post('/', 'LoginController@authenticate')->name('login.login');
Route::match(['get', 'post'], 'logout', 'LoginController@logout')->name('login.logout');
// Admin画面
Route::get('admin', 'AdminController@index')->name('admin.index');
// User画面
Route::get('user', 'UserController@index')->name('user.index');
```

## 作成

### Model

```php:app/Admin.php
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    //
}
```

#### Migrations

```php:create_admins_table
public function up()
{
    Schema::create('admins', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();

        $table->string('username')->unique();
        $table->string('password');
        $table->rememberToken();
    });
}
```

#### Seeds

```php:database/seeds/AdminTableSeeder.php
public function run()
{
    DB::table('admins')->insert([
        'username' => 'admin',
        'password' => Hash::make('Passw0rd'),
        'remember_token' => '',
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);
}
```

```php:database/seeds/UserTableSeeder.php
public function run()
{
    DB::table('users')->insert([
        'name' => 'user',
        'email' => 'user@example.com',
        'password' => Hash::make('User2017'),
        'remember_token' => '',
        'created_at' => Carbon::now(),
        'updated_at' => Carbon::now(),
    ]);
}
```

```php:database/seeds/DatabaseSeeder.php
public function run()
{
    $this->call(AdminTableSeeder::class);
    $this->call(UserTableSeeder::class);
}
```

### View

```php:resources/views/login.blade.php
<form action="/" method="post">
    {{csrf_field()}}
    <label>ID: <input type="text" name="id" id="id"></label>
    <label>Password: <input type="password" name="password"> </label>
    <button type="submit">Login</button>
</form>
```

### Middleware

```php:app/Http/Kernel.php
protected $routeMiddleware = [
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    // 追加
    'auth.check.admin' => \App\Http\Middleware\CheckAdminAuthenticate::class,
    'auth.check.user' => \App\Http\Middleware\CheckAuthenticate::class,
    // 追加ここまで
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
```

```php:config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    // 追加
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
    ],
],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],
    // 追加
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Admin::class,
    ],
],
'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],
    // 追加
    'admins' => [
        'provider' => 'admins',
        'table' => 'password_resets',
        'expire' => 60,
    ],
],
```

```php:app/Http/Middleware/CheckAuthenticate.php
public function handle($request, Closure $next)
{
    if (Auth::guard('web')->check() === false) {
        return redirect()->route('login.index');
    }
    return $next($request);
}
```

```php:app/Http/Middleware/CheckAdminAuthenticate.php
public function handle($request, Closure $next)
{
    if (Auth::guard('admin')->check() === false) {
        return redirect()->route('login.index');
    }
    return $next($request);
}
```

### Controller

```php:app/Http/Controllers/LoginController.php
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $username = 'id';

    public function username()
    {
        return $this->username;
    }

    public function index()
    {
        return view('login');
    }

    public function logout(Request $request)
    {
        if (Auth::guard('web')->check()) {
            Auth::guard('web')->logout();
        }
        if (Auth::guard('admin')->check()) {
            Auth::guard('admin')->logout();
        }
        return redirect()->route('login.index');
    }

    public function authenticate(Request $request)
    {
        // User ログイン
        Auth::guard('web');
        $this->validateLogin($request);
        $id = $request->input('id');
        $password = $request->input('password');
        if (Auth::attempt(['id' => $id, 'password' => $password])) {
            return redirect()->route('user.index');
        }

        // Admin ログイン
        Auth::guard('admin');
        $this->username = 'username';
        if (Auth::guard('admin')->attempt(['username' => $id, 'password' => $password])) {
            return redirect()->route('admin.index');
        }

        return redirect()->back();
    }
}
```

```php:app/Http/Controllers/AdminController.php
class AdminController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth.check.admin');
    }

    public function index()
    {
        return 'AdminPage!';
    }
}
```

```php:app/Http/Controllers/UserController.php
class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth.check.user');
    }

    public function index()
    {
        return 'UserPage!';
    }
}
```

## 確認

```bash:bash
$ php artisan migrate --seed
$ php artisan serve
# http://localhost:8000/ へアクセス
```