Laravel5.5LTS: multi Auth

Laravelの`マルチ認証`の記事はたいてい`コピペ`を多用してます。
`コピペの多用`は`バグの温床`になりやすいので`条件分岐`で極力減らしてみました。

前提条件: Laravel5.5 インストール

[Laravel5.5認証（公式）
](https://readouble.com/laravel/5.5/ja/authentication.html)

####.env（DB設定）
```php:.env
//DB接続設定
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={データベース名}
DB_USERNAME={user}
DB_PASSWORD={password}
```
####AppServiceProvider.php
```php:app/Providers/AppServiceProvider.php
//DBの191文字超えエラー回避設定
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
++      \Illuminate\Support\Facades\Schema::defaultStringLength(191);
    }
}
```
####Authとマイグレーション

```php:認証のインストールとマイグレーション
//認証のインストール
$ php artisan make:auth
//認証用DBテーブル作成
$ php artisan migrate
//-> DBにusers・password_resetsテーブルが作成されたか確認。
```
####Adminモデルの生成

```php:Adminモデルとマイグレーションの生成
$ php artisan make:model Admin -m
//-> App/Admin.php
//-> database/migrations/{日付}_create_admins_table.php
```

####Adminモデル
```php:app/Admin.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable //modify
{
    use Notifiable;
    protected $fillable = [
        'name', 'email', 'password',
    ];
    protected $hidden = [
        'password', 'remember_token',
    ];
}
```

####Adminテーブルのマイグレーション
`create_users_table.php`の内容を`コピーして変更`

```php:database/migrations/{日付}create_admins_table.php
<?php
use Illuminate\Support\Facades\Schema;
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');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('admins');
    }
}
```

####adminテーブルのマイグレーション実行

```php:
$ php artisan migrate
//adminsテーブルが生成されたか確認
```

####tinker -> adminユーザーの生成
[password_hash（公式）](https://www.php.net/manual/ja/function.password-hash.php)

```php:tinker
$ php artisan tinker
>>> $admin = new App\Admin;
>>> $admin->name = 'admin';
>>> $admin->email = 'test@test.com';
>>> $admin->password = password_hash('password', PASSWORD_DEFAULT);
>>> $admin->save();
//phpMyadmin等でユーザー生成の確認
```

####[auth.php（公式）](https://readouble.com/laravel/5.5/ja/authentication.html)
`admin側の定義の追加`

```php:config/auth.php
<?php
return [
    'defaults' => [
        'guard' => 'user', //modify
        'passwords' => 'users',
    ],
    'guards' => [
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
        'user' => [         //modify
            'driver' => 'session',
            'provider' => 'users',
        ],
 +       'admin' => [
 +           'driver' => 'session',
 +           'provider' => 'admins',
 +      ],
    ],
    '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,
+        ],
    ],
];
```
Laravelの認証機能は`「ガード」`と`「プロバイダ」`を中心概念として構成されています。ガードは`各リクエストごと`に、`どのようにユーザーを認証するか`を定義します。たとえば、Laravelには`セッションストレージ`と`クッキー`を使いながら状態を維持する`sessionガード`が用意されています。

####ログインしていないときのリダイレクト先

```php:app/Exceptions/Handler.php
<?php
namespace App\Exceptions;

use Exception;
use Illuminate\Auth\AuthenticationException; //add
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    protected $dontReport = [
        //
    ];
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];
    public function report(Exception $exception)
    {
        parent::report($exception);
    }
    public function render($request, Exception $exception)
    {
        return parent::render($request, $exception);
    }
    //未ログイン時 -> ガード属性のログインページにリダイレクト
    public function unauthenticated($request, AuthenticationException $exception)
    {
        if($request->expectsJson()){
            return response()->json(['message' => $exception->getMessage()], 401);
        }
        if (in_array('admin', $exception->guards())) {
            return redirect()->guest(route('admin.login'));
        }
        return redirect()->guest(route('login'));
    }
}
```

####ルーティング

```php:route/web.php
<?php
Auth::routes();
// User 認証不要
Route::get('/', function () { 
    return redirect('home');
});
// User ログイン後
Route::group(['middleware' => 'auth:user'], function() {
    Route::get('home', 'HomeController@index')->name('home');
});
// Admin 認証不要
Route::group(['prefix' => 'admin'], function() {                                  
    Route::get('login', 'Admin\LoginController@showLoginForm')->name('admin.login');
    Route::post('login', 'Admin\LoginController@login');     
});                                                                                 
// Admin ログイン後
Route::group(['prefix' => 'admin', 'middleware' => 'auth:admin'], function() {
    Route::post('logout', 'Admin\LoginController@logout')->name('admin.logout');
    Route::get('home', 'HomeController@index')->name('admin.home');
});
```
####コントローラーの構成
`adminフォルダ`を作成して`Authフォルダ`から`LoginController.php`をコピーします。
![WS000039.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455147/b715d99e-07c0-ebe2-623d-8e910555321f.png)

####LoginController.php（Auth）
```php:app/Http/Controller/Auth/LoginController
<?php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;            //add
use Illuminate\Support\Facades\Auth;    //add

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/home';

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
    public function logout(Request $request)
    {
        Auth::guard('user')->logout();  //modify
        return redirect('login');  //modify
    }
}
```
####LoginController.php（Admin）
```php:app/Http/Controller/Admin/LoginController.php
<?php
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;            //add
use Illuminate\Support\Facades\Auth;    //add

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = 'admin/home'; //modify

    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout');//modify
    }
    public function showLoginForm()
    {
        //login.blade.phpは共用
        return view('auth.login');  //modify
    }
    protected function guard()
    {
        return Auth::guard('admin');  //modify
    }
    //以下追加
    public function logout(Request $request)
    {
        Auth::guard('admin')->logout();
        return redirect('admin/login');
    }
}
```
####ログイン後のリダイレクト先
```php:app/Http/Middleware/RedirectIfAuthenticated.php
<?php                                                               
namespace App\Http\Middleware;                                      

use Closure;                                                        
use Illuminate\Http\Request;                                        
use Illuminate\Support\Facades\Auth;                                
                                                                    
class RedirectIfAuthenticated                                       
{                                                                   
    public function handle($request, Closure $next, $guard = null) {
        if (Auth::guard($guard)->check()) {
            switch ($guard) {
                case 'admin':
                    return redirect('/admin/home');
                    break;
                default:
                    return redirect('/home');
            }
        }
        return $next($request);
    }                                                               
}                                                                   
```
####ヘルパー関数の定義（権限を便利に利用するための関数）
参考：[ヘルパ関数の自作](https://qiita.com/Fell/items/8e01db9aa8c3b0cf0cf9)

```php:helpers.php
<?php
use Illuminate\Support\Facades\Auth;

//ユーザー情報の取得
if (! function_exists('userInfo')) {
    function userInfo() {
        return Auth::guard()->user();
    }
}
//ログインしているか？
if (! function_exists('isLogin')) {
    function isLogin() {
        return !empty(userInfo());
    }
}
//ユーザーの属性を取得する
if (! function_exists('getUserType')) {
    function getUserType() {
        if (isLogin()) {
            switch (get_class(userInfo())) {
                case 'App\Admin':
                    $userType = 'Admin';
                    break;
                default:
                    $userType = 'User';
            }
        } else {
            $userType = 'Guest';
        }
        return $userType;
    }
}
//現在のページが管理者用か？-> adminのRouteには'admin'のプレフィクスをつける。
if (! function_exists('isAdminRoute')) {
    function isAdminRoute() {
        return strpos(\Route::currentRouteName(), 'admin') !== false;
    }
}
if (! function_exists('isAdminLogin')) {
   function isAdminLogin() {
       return isAdminRoute() && strpos(\Route::currentRouteName(), 'login') !== false;
   }
}
```

####コントローラーの使用例（条件分岐の例）
`HomeController.php`

```php:app/Http/Controller/HomeController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;    //add

class HomeController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }
    public function index()
    {
        //string: ユーザー属性を取得してテスト
        $userType = getUserType();
        dd($userType);
        return view('home');
    }
}
```

####ビュー
getUserType()で属性を判断できます。

![WS000041.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455147/838fd367-c4c1-c3cd-42ba-d37f9dd05c52.jpeg)
`ビュー`はコピペせずに条件分岐で`user`と`admin`の制御を行います。

####app.blade.php

```php:resources/views/layouts/app.blade.php
～～略～～
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
@if (isAdminRoute())
<style>body{background-color: tomato;}</style>
@endif
～～略～～
<!-- Branding Image -->
@if (!isLogin())
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}</a>
@elseif (isLogin() && getUserType() == 'User')
<a class="navbar-brand" href="{{ route('home') }}">
{{ config('app.name', 'Laravel') }}</a>
@elseif (isLogin() && getUserType() == 'Admin')
<a class="navbar-brand" href="{{ route('admin.home') }}">
{{ config('app.name', 'Laravel') }}</a>
@endif
～～略～～
<!-- Authentication Links -->
@if (isLogin() && !isAdminLogin())
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
{{ userInfo()->name }} <span class="caret"></span>
</a>
@elseif (!isLogin())
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
@elseif (!isLogin() || isAdminLogin())
<li><a href="{{ route('admin.login') }}">Login</a></li>
@endif
@if (isLogin() && getUserType() == 'User')
<ul class="dropdown-menu">
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">Logout</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
@elseif (isLogin() && getUserType() == 'Admin')
<ul class="dropdown-menu">
<li>
<a href="{{ route('admin.logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">Logout</a>
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
@endif
～～略～～
```
####Login.blade.php

```php:resources/views/auth/login.blade.php
～～略～～
@if ((!isLogin() && isAdminRoute()) || (getUserType() == 'User' && isAdminLogin()))
<form class="form-horizontal" method="POST" action="{{ route('admin.login') }}">
@elseif (!isLogin() && !isAdminRoute())
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
@endif
{{ csrf_field() }}
```
####Home.blade.php

```php:resources/views/home.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<?php $user = userInfo(); ?>
{{ $user->name . 'さん: ' }}
{{ $user->id . ', ' }}
{{ $user->email }}
{{ '属性: ' . getUserType() }}
</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
```
いかがでしたでしょうか？
`条件分岐`のやり方を押さえることができれば`改変・応用`は簡単です。
少なくとも１つ機能を増やすために`ファイル間を移動`することは`確実に減る`でしょう。
`改修`や`デバッグ作業`も同様です。
`管理権限を増やす`のも楽だと思います。

LGTMお願いします！
ストックのついでにお願いします！
モチベーションがあがります！

補足
オブジェクト指向を学び始めてから、この記事は中途半端だったなあと思います笑
１．UIはBaseを継承させる
２．Factoryで継承済みUIを自動でチョイスさせる

UI内でIfを使いまくってる部分を直したいｗ