Laravel5.6でusersテーブル以外のテーブルを利用して認証を行う(マルチAuth)

Laravelにはデフォルトで認証機能を利用出来る様になっていますが、別のテーブルを使って認証を行い為為方法をまとめます。
思ったより詰まってしまったので次回以降はスムーズに利用で出来るようにしたいです。
また、最終ログイン時刻の実装方法も一工夫必要だったのでまとめておきます。
(パスワード変更は未検証の為記載していません。)


# 動作環境について
OS:macOS High Sierra
version: 10.13.6

Docker(Engine):18.09.0


#### 対象ブラウザ
GoogleChrome:71以上

#### 環境
centOS:7.5
Laravel:5.6
PHP:7.2
MySQL:5.7
APache2.4

# 参考資料
[Laravel 5.4/5.5 で Multi-Auth を実装する](https://qiita.com/s_of_p/items/3c066f56123cb05a24f1)
[Laravelの認証ログイン時にデータベースへ最終ログイン日時の書き込みを行う](https://www.ritolab.com/entry/54)
[Laravel5.4でマルチログインを実装する](https://www.willstyle.co.jp/blog/907/)



# 基本の認証機能

まずはデフォルトで用意されている認証機能を作成します。

```shell-session
[root@87c2be02241a shop]# php artisan make:auth
Authentication scaffolding generated successfully.
```

これで下記のファイルが生成されます。


```shell-session
/app/Http/Controllers/HomeController.php
/resources/views/home.blade.php
/resources/views/layouts/app.blade.php
/resource/views/auth/login.blade.php
/resource/views/auth/register.blade.php
/resource/views/auth//passwords/email.blade.php
/resource/views/auth//passwords/reset.blade.php
```


また、web.phpに下記が追記されます。

```PHP
// Auth
Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
```

「Auth::routes();」の部分はマルチAuth実装の為に下記に書き換えておきます。
*「/vendor/laravel/framework/src/Illuminate/Routing/Router.php」内の記述を呼び出しています。

```PHP
// Auth
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');

// Password Reset Routes...
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');
```

既にデフォルトの「users」テーブルが作成出来ているのならすぐに認証機能を利用することが出来ます。

## adminテーブルの作成

今度は新しいテーブルを作ってそのテーブル内のデータから認証を行います。
新しい管理者ユーザーのテーブルは「admin」とします。

```shell-session
[root@87c2be02241a shop]# php artisan make:migration create_admin_table
Created Migration: 2018_11_11_162121_create_admin_table
```

マイグレーションファイル内でテーブル定義を下記の様に記述します。

```PHP
Schema::create('admin', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->tinyInteger('role')->default(0)->index('index_admin_role')->comment('ロール');
    $table->rememberToken();
    $table->timestamp('last_login_at')->nullable()->comment('最終ログイン日時');
    $table->timestamps();
    $table->softDeletes();
    });
```

************************
*注意！
私は最初idカラムを「admin_id」という名前にしていましたが、これを「id」に変更しました。
管理者ユーザーの登録の際には上手くデータが入るのですが、ログインの際には「id」カラムを参照しようとする為下記のエラーが出てきます。
usersテーブルと共通のカラムは名前を変更しない方が良いです。

```shell-session
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'where clause' (SQL: select * from `admin` where `id` = 1 limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'where clause' (SQL: select * from `admin` where `id` = 1 limit 1) 
```

************************


## マイグレーション実行

```shell-session
[root@87c2be02241a shop]# php artisan migrate
Migrating: 2018_11_11_162121_create_admin_table
Migrated:  2018_11_11_162121_create_admin_table
```

「admin」テーブルが作成されます。

## auth.phpの編集
「admin」に当たる部分を下記の通りに追記します。
*コメントアウトはデフォルトで記載されています。

```PHP
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
    ],

    'admin' => [
        'driver' => 'session',
        'provider' => 'admin',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

   'admin' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],

    'admin' => [
        'provider' => 'admin',
        'table' => 'password_resets',
        'expire' => 60,
    ],
],
```

## Modelの作成
・adminテーブル用のModelを作成します。

```shell-session
[root@87c2be02241a shop]# php artisan make:model Models/Admin
Model created successfully.
```

Modelファイルは「/app/User.php」を元に書きます。
「Illuminate\Foundation\Auth\User as Authenticatable」をuseすることと、継承元を「Authenticatable」にする必要があります。


```PHP
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    use Notifiable;

    protected $table = 'admin';
    public $timestamps = true;

    protected $fillable = [
        'name',
        'email',
        'password',
        'role',
        'updated_at'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}
```



## adminテーブル用の認証機能の実装

Modelを作ったら、今度はusersテーブル様に作成されたview、controllerファイルをコピペしてadminテーブル用の認証機能を実装します。

下記の構成になる様にファイルをコピペします。
(ファイル名は全く同じで大丈夫です。)


```shell-session
/app/Http/Controllers/Admin/Auth/LoginController.php
/app/Http/Controllers/Admin/Auth/ForgotPasswordController.php
/app/Http/Controllers/Admin/Auth/RegisterController.php
/app/Http/Controllers/Admin/Auth/ResetPasswordController.php
/app/Http/Controllers/Admin/HomeController.php

/resources/views/admin/passwords/reset.blade.php
/resources/views/admin/passwords/email.blade.php
/resources/views/admin/home.blade.php
/resources/views/admin/login.blade.php
/resources/views/admin/register.blade.php
/resources/views/layouts/admin.blade.php
```

### web.phpにadmin認証用のルーティングを追記
下記を追記します。
(パスワードリセットは未検証の為、コメントアウトしています。)

```PHP
Route::group(['prefix' => 'admin'], function(){
    Route::get('login', 'Admin\Auth\LoginController@showAdminLoginForm')->name('admin_login');
    Route::post('login', 'Admin\Auth\LoginController@login')->name('admin_login_post');
    Route::post('logout', 'Admin\Auth\LoginController@logout')->name('admin_logout');

    // Registration Routes...
    Route::get('register', 'Admin\Auth\RegisterController@showRegistrationForm')->name('admin_register');
    Route::post('register', 'Admin\Auth\RegisterController@register')->name('admin_register_post');

    // Password Reset Routes...
    //Route::get('password/reset', 'Admin\Auth\ForgotPasswordController@showLinkRequestForm')->name('admin_password_request');
    //Route::post('password/email', 'Admin\Auth\ForgotPasswordController@sendResetLinkEmail')->name('admin_password_email');
    //Route::get('password/reset/{token}', 'Admin\Auth\ResetPasswordController@showResetForm')->name('password.reset');
    //Route::post('password/reset', 'Admin\Auth\ResetPasswordController@reset');

    Route::get('home', 'Admin\HomeController@index')->name('admin_home');
});
```

## viewファイルの編集

viewファイルは基本的にリンクやフォームアクションのrouteのnameを変更しているだけです。


対象:admin.blade.php

```PHP

-href="{{ route('login') }}"
+href="{{ route('admin_login') }}"

-href="{{ route('register') }}"
+href="{{ route('admin_register') }}"

-href="{{ route('logout') }}"
+href="{{ route('admin_logout') }}"

-action="{{ route('logout') }}"
+action="{{ route('admin_logout') }}"
```


対象:register.blade.php

```PHP
-action="{{ route('register') }}"
+action="{{ route('admin_register_post') }}"
```

対象:login.blade.php

```PHP

-action="{{ route('login') }}"
+action="{{ route('admin_login_post') }}"

-href="{{ route('password_request') }}"
+href="{{ route('admin_password_request') }}
```

継承元も変更します。


対象:
home.blade.php
login.blade.php
register.blade.php

```PHP

-@extends('layouts.app')
+@extends('layouts.admin')
```

## Contollerファイルの編集

Controllerファイルはそのまま内容を抜粋します。
guardの指定に注意が必要です。

registとloginのControllerではコンストラクタで「middleware('guest:admin')」を指定しています。
また、guardメソッドで「admin」を指定しています。

認証後のhome画面に関するHomeControllerでは、コンストラクタで「middleware('auth:admin')」を指定しています。
また、guardメソッドで同様に「admin」を指定しています。


対象:RegisterController.php

```PHP
<?php

namespace App\Http\Controllers\Admin\Auth;

use App\Models\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Auth;

class RegisterController extends Controller
{

    public function showRegistrationForm()
    {
        return view('admin.register');
    }
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/admin/home';

    public function guard()
    {
        return Auth::guard('admin');
    }

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\Admin
     */
    protected function create(array $data)
    {
        return Admin::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

```

対象:LoginController.php

```PHP
?php

namespace App\Http\Controllers\Admin\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use App\Events\Logined;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    protected $maxAttempts = 3;     // ログイン試行回数(回数)
    protected $decayMinutes = 10;   // ログインロックタイム(分)

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/admin/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout');
    }

    public function guard()
    {
        return Auth::guard('admin');
    }

    public function showAdminLoginForm()
    {
        return view('admin.login');
    }

    /**
     * ログイン後の処理
     * @param Request $request
     * @param $user
     */
    protected function authenticated(Request $request, $user)
    {
        // ログインイベントを発火させ、最終ログイン日時を記録する
        event(new Logined());
    }
}


```


対象:HomeController.php

```PHP
<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:admin');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('admin.home');
    }
}

```

## ミドルウェアの編集

ミドルウェアの「/app/Http/Middleware/RedirectIfAuthenticated.php」を編集をします。
新しく設定したguradが使用されている時にリダイレクト先を編集します。


```PHP
<?php

namespace App\Http\Middleware;

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

class RedirectIfAuthenticated
{
    /**
     * 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)
    {
        $redir = '/home';
        switch ($guard)
        {
            case "admin":
                $redir ='/admin/home';
                break;
            default:
                $redir ='/home';
                break;
        }

        if (Auth::guard($guard)->check()) {
            //return redirect('/home');
            return redirect($redir);
        }

        return $next($request);
    }
}

```

## Handler.phpの編集
「/app/Exceptions/Handler.php」を編集します。
adminのguardの時にrouteを変更しています。

「use Illuminate\Auth\AuthenticationException;」を追記してください。
*下記修正部分のみ抜粋

```PHP

    /**
     * Convert an authentication exception into an unauthenticated response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Auth\AuthenticationException  $exception
     * @return \Illuminate\Http\Response
     */
    protected function unauthenticated($request, AuthenticationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json(['error' => 'Unauthenticated.'], 401);
        }
        if (in_array('admin', $exception->guards())) {
            return redirect()->guest(route('admin_login'));
        }
        return redirect()->guest(route('login'));
    }

```

## 最終ログイン時刻の登録処理の修正


参考に記載されていた、最終ログイン時刻を登録する処理を改造したものです。
最終ログイン時刻を登録する為に
「/app/Listeners/LastLoginListener.php」のhundler()を編集します。
このイベントではマルチAuthが適用されない為多少強引にAdminテーブル側に時間を登録する必要があります。

```PHP
public function handle(Logined $event)
{
    $request_url = $_SERVER['REQUEST_URI'];
    if($request_url == "/admin/login")
    {
        $email = $_POST['email'];
        // クエリ
        $admin = \App\Models\Admin::where('email',$email)->first();
        $admin->last_login_at = Carbon::now();
        // 保存
        $admin->save();
    }
    else
    {
        // userテーブルを使った最終ログイン時刻の書き込み
        $user = Auth::user();
        $user->last_login_at = Carbon::now();
        $user->save();
    }
}
```

これでユーザー登録からログインまではマルチ認証が出来る様になります。