Laravel 5.7 マルチ認証で管理者とユーザーの認証を分ける

[こちらはブログでも公開しています。](https://ksysnote.com/php/laravel-multi-auth/)

今回はユーザーと管理者の認証を分ける方法とマルチ認証というやり方があるようなのですが、他のサイトを確認しながらやってみたのですが少しうまく行かなかったのでまとめてみました。

今回対応するのは、
<ul>
 	<li>認証をユーザーと管理者で分ける</li>
 	<li>管理画面は /admin/以下に配置する</li>
 	<li>アカウントと登録の２つを実装する（パスワードリセット、メール認証はなし）</li>
 	<li>ユーザーと管理者で管理するセッションを分ける</li>
</ul>
<h4>環境：</h4>
OS : macOS Mojave 10.14.2

MAMP : 5.1

Laravel : 5.7.6
<h2>ユーザ認証までを作成する</h2>
<h3>Laraveのプロジェクトを作成する</h3>
composerやMAMPなどLaravelの環境については「<a href="https://ksysnote.com/php/laravel-mamp-setup/">Laravel開発：１．環境構築をMAMPを使用して作成する</a>」に記載しているので、こちら参考にして下さい。

・<span class="marker">sample</span>という名でプロジェクトを作成します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">$ composer create-project laravel/laravel --prefer-dist sample
$ cd sample
$ chmod -R 777 storage
$ chmod -R bootstrap/cache</pre>
・VirtualHostの設定

MAMPを利用している場合は <span class="marker">MAMP/con/apache/extra/httpd-vhosts.conf</span> を編集します。

・DBの設定

phpMyAdminから今回のプロジェクト用に「<span class="marker">sample</span>」という名でdatabaseを作成します。

・.envの設定

以下の値を変更して下さい。
<pre class="EnlighterJSRAW" data-enlighter-language="null">APP_URL=http://localhost:8888
DB_PORT=8889
DB_DATABASE=sample
DB_USERNAME=root
DB_PASSWORD=root</pre>
この時点で <span class="marker">http://localhost:8888/</span> を表示するとLaravelの画面が表示されます。
<h3>認証機能の追加</h3>
ユーザの認証機能を追加します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">$ php artisan make:auth</pre>
こちらのコマンドに認証に必要な機能が用意されます。

実際にマイグレーションをするとテーブルが作成されるのでユーザ認証の実装は完成となります。
<h2>管理者認証を追加する</h2>
引き続き管理者の認証を作成していきます。
<h3>モデルとマイグレーションファイルの作成</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="null">$ php artisan make:model Admin -m</pre>
こちらのコマンドでAdminモデルとマイグレションファイルが作成されます。

ユーザーのファイルを参考に編集していきます。
<h4>Adminモデル</h4>
app/User.phpを参考にapp/Admin.phpを編集します。User.phpからそのままコピーをしてモデル名だけを変更します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">&lt;?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}</pre>
<h4>マイグレーションファイル</h4>
database/migrations内にadmins_tableのマイグレションファイルがあります。

こちらもusers_tableを参考にカラムを設定します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">public function up()
{
    Schema::create('admins', function (Blueprint $table) {
        $table-&gt;increments('id');
        $table-&gt;string('name');
        $table-&gt;string('email')-&gt;unique();
        $table-&gt;timestamp('email_verified_at')-&gt;nullable();
        $table-&gt;string('password');
        $table-&gt;rememberToken();
        $table-&gt;timestamps();
    });
}</pre>
<h4>マイグレーション</h4>
マイグレーションを実行すれば必要なテーブルが作成されます。
<pre class="EnlighterJSRAW" data-enlighter-language="null">php artisan migrate</pre>
<h3>認証の設定を編集する</h3>
config/auth.phpに認証の設定情報があります。

ユーザーの分は既に設定されていますので、管理者用の分を追加していきます。

・デフォルトのguardがwebとなっているので、userに変更します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">'defaults' =&gt; [
    'guard' =&gt; 'user',     // webからuserに変更
    'passwords' =&gt; 'users',
],</pre>
・管理者のguardを追加します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">'guards' =&gt; [
　　　　　　　　// webからuserに変更
    'user' =&gt; [
        'driver' =&gt; 'session',
        'provider' =&gt; 'users',
    ],

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

    // 追加
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],</pre>
・プロバイダーの追加
<pre class="EnlighterJSRAW" data-enlighter-language="null">'providers' =&gt; [
    'users' =&gt; [
        'driver' =&gt; 'eloquent',
        'model' =&gt; App\User::class,
    ],

    // 追加
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Admin::class,
    ]

],</pre>
・パスワードリセットの設定（今回はリセットについては対応していません）
<pre class="EnlighterJSRAW" data-enlighter-language="null">'passwords' =&gt; [
    'users' =&gt; [
        'provider' =&gt; 'users',
        'table' =&gt; 'password_resets',
        'expire' =&gt; 60,
    ],
    // 追加
    'admins' =&gt; [
        'provider' =&gt; 'admins',
        'table' =&gt; 'password_resets',
        'expire' =&gt; 15
    ]
],</pre>
<h3>Controllerの作成</h3>
管理者用のディレクトリを作成し、ユーザーのコントローラを利用しながら作成していきます。

app/Http/ControllersにAdminディレクトリを作成します。

そこに、app/Http/Controllers/Authとapp/Http/Controllers/HomeController.phpをコピーします。
<pre class="EnlighterJSRAW" data-enlighter-language="null">app
├── Http
    ├── Controllers
        ├── Admin  // ディレクトリを作成
            ├── Auth // Authディレクトリのコピー
            │   ├── ForgotPasswordController.php
            │   ├── LoginController.php
            │   ├── RegisterController.php
            │   ├── ResetPasswordController.php
            │   └── VerificationController.php
            └── HomeController.php // HomeController.phpのコピー</pre>
<h4>LoginController</h4>
<ol>
 	<li>namespaceにAdminを追加</li>
 	<li>redirectToにログイン後のリダイレクト先を設定</li>
 	<li>ログイン画面で管理者用のテンプレートを呼び出すように設定</li>
 	<li>middlewareの設定を削除し、routeで管理するように変更</li>
 	<li>guardをadminに設定</li>
 	<li>logout時のリダイレクト先を設定</li>
</ol>
<pre class="EnlighterJSRAW" data-enlighter-language="null">&lt;?php
namespace App\Http\Controllers\Admin\Auth; // Adminを追加

use App\Http\Controllers\Admin\Auth;   // 追加
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

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;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/admin/home';　　　　// ログイン後のリダイレクト先

    // ログイン画面
    public function showLoginForm()
    {
        return view('admin.auth.login'); //管理者ログインページのテンプレート
    }

    protected function guard()
    {
        return \Auth::guard('admin'); //管理者認証のguardを指定
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this-&gt;guard()-&gt;logout();

        $request-&gt;session()-&gt;invalidate();

        return $this-&gt;loggedOut($request) ?: redirect('/admin/');  // ログアウト後のリダイレクト先
    }
}
</pre>
<h4>RegisterController</h4>
<ol>
 	<li>namespaceにAdminを追加</li>
 	<li>redirectToに管理者登録後のリダイレクト先を設定する</li>
 	<li>登録画面に管理者用のテンプレートを指定するように設定する</li>
 	<li>バリデーションでメールアドレスをadminsテーブルでユニークになるように設定</li>
 	<li>作成するモデルAdminに変更する</li>
 	<li>guardをAdminに設定する</li>
</ol>
<pre class="EnlighterJSRAW" data-enlighter-language="null">&lt;?php
namespace App\Http\Controllers\Admin\Auth;　　// Adminの追加

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

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | 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 showRegisterForm()
    {
        return view('admin.auth.register');  // 管理者用テンプレート
    }

    /**
     * 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' =&gt; ['required', 'string', 'max:255'],
            'email' =&gt; ['required', 'string', 'email', 'max:255', 'unique:admins'], // adminsテーブに変更
            'password' =&gt; ['required', 'string', 'min:6', 'confirmed'],
        ]);
    }

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

    /**
     * Get the guard to be used during registration.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return \Auth::guard('admin'); //管理者認証のguardを指定
    }
}
</pre>
<h4>HomeController</h4>
<ol>
 	<li>namespaceにAdminを追加する</li>
 	<li>管理者用のテンプレートを設定する</li>
</ol>
<pre class="EnlighterJSRAW" data-enlighter-language="null">&lt;?php
namespace App\Http\Controllers\Admin;  // Adminの追加

use App\Http\Controllers\Controller;   // 追加
use Illuminate\Http\Request;


class HomeController extends Controller
{

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('admin.home');   // 管理者用のテンプレート
    }
}
</pre>
<h4>その他</h4>
ForgotPasswordController、ResetPasswordController、VerificationControllerについてはnamepspaseを変更する

（対応時に加筆します）
<h3>Viewを追加</h3>
管理者用にadminを追加して、ユーザーのviewを参考に作成していきます。

resources/viewsにadminディレクトリを作成する

authディレクトリとhome、welcomeをコピーする

管理者用のレイアウトにlayouts/app.blade.phpをadmin.blade.phpとしてコピーする
<pre class="EnlighterJSRAW" data-enlighter-language="null">views
├── admin
│   ├── auth
│   │   ├── login.blade.php
│   │   ├── passwords
│   │   │   ├── email.blade.php
│   │   │   └── reset.blade.php
│   │   ├── register.blade.php
│   │   └── verify.blade.php
│   ├── home.blade.php
│   └── welcome.blade.php
├── layouts
    ├── admin.blade.php
    └── app.blade.php
</pre>
<h4>layouts/admin</h4>
管理者として分かりやすいように背景色を変更します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">&lt;!-- Styles --&gt;
&lt;link href="{{ asset('css/app.css') }}" rel="stylesheet"&gt;
&lt;style&gt;body{background-color: #26263c;}&lt;/style&gt;</pre>
管理者のログインチェックができるように変更します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">@if(Auth::guard('admin')-&gt;check())
    &lt;li class="nav-item dropdown"&gt;
        &lt;a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre&gt;
            {{ Auth::guard('admin')-&gt;user()-&gt;name }} &lt;span class="caret"&gt;&lt;/span&gt;
        &lt;/a&gt;

        &lt;div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"&gt;
            &lt;a class="dropdown-item" href="{{ route('admin.logout') }}"
               onclick="event.preventDefault();
                             document.getElementById('logout-form').submit();"&gt;
                {{ __('Logout') }}
            &lt;/a&gt;

            &lt;form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;"&gt;
                @csrf
            &lt;/form&gt;
        &lt;/div&gt;
    &lt;/li&gt;
@else
    &lt;li class="nav-item"&gt;
        &lt;a class="nav-link" href="{{ route('admin.login') }}"&gt;{{ __('Login') }}&lt;/a&gt;
    &lt;/li&gt;
    @if (Route::has('register'))
        &lt;li class="nav-item"&gt;
            &lt;a class="nav-link" href="{{ route('admin.register') }}"&gt;{{ __('Register') }}&lt;/a&gt;
        &lt;/li&gt;
    @endif
@endif</pre>
分かりづらいですが、@guest @endguest内を変更しています。

Auth::guard('admin')-&gt;check()・・・管理者としてログインしているかをチェックします。

Auth:guard('admin')-&gt;user()・・・ログインしている管理者情報を取得します。

route()の指定はadminに変更します。
<h4>その他</h4>
<ol>
 	<li>adminのレイアウトを指定</li>
 	<li>route()の指定でadminに変更</li>
</ol>
<h3>その他</h3>
<h4>ルーティングの設定</h4>
routes/web.phpに管理画面の設定を追加します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">Route::group(['prefix' =&gt; 'admin', 'middleware' =&gt; 'guest:admin'], function() {
    Route::get('/', function () {
        return view('admin.welcome');
    });
    Route::get('login', 'Admin\Auth\LoginController@showLoginForm')-&gt;name('admin.login');

    Route::get('login', 'Admin\Auth\LoginController@showLoginForm')-&gt;name('admin.login');
    Route::post('login', 'Admin\Auth\LoginController@login')-&gt;name('admin.login');

    Route::get('register', 'Admin\Auth\RegisterController@showRegisterForm')-&gt;name('admin.register');
    Route::post('register', 'Admin\Auth\RegisterController@register')-&gt;name('admin.register');

    Route::get('password/rest', 'Admin\Auth\ForgotPasswordController@showLinkRequestForm')-&gt;name('admin.password.request');
});

Route::group(['prefix' =&gt; 'admin', 'middleware' =&gt; 'auth:admin'], function(){
    Route::post('logout', 'Admin\Auth\LoginController@logout')-&gt;name('admin.logout');
    Route::get('home', 'Admin\HomeController@index')-&gt;name('admin.home');
});</pre>
groupを利用してまとめて設定をしています。
<h4>エラー時の設定</h4>
app/Exceptions/Handler.phpに管理画面でエラーになったときにリダイレクト先を指定します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">protected function unauthenticated($request, AuthenticationException $exception)
{
    if ($request-&gt;expectsJson()) {
        return response()-&gt;json(['error' =&gt; 'Unauthenticated.'], 401);
    }
    if (in_array('admin', $exception-&gt;guards())) { // ここから
        return redirect()-&gt;guest('admin/login');
    } // ここまで追記
    return redirect()-&gt;guest(route('login'));
}</pre>
<h4>セッションの設定</h4>
管理者とユーザで管理するセッションを分けるようにします。

.envにキーを設定します。
<pre class="EnlighterJSRAW" data-enlighter-language="null">SESSION_COOKIE=auth
SESSION_COOKIE_ADMIN=auth-admin</pre>
config/session.phpでadminのときにキーを設定し直すようにします。
<pre class="EnlighterJSRAW" data-enlighter-language="null">// 配列を一度保持するように書き換えます。
$conf = [
// 元々記載のあった配列の中身
];

// 管理画面のセッションクッキーを変更する
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
if (strpos($uri, '/admin/') === 0 || $uri === '/admin') {
    $conf['cookie'] = env(
        'SESSION_COOKIE_ADMIN',
        str_slug(env('APP_NAME', 'laravel'), '_').'_admin_session'
    );
}
return $conf;
</pre>
<h3>最後に</h3>
以上、ユーザと管理画面の認証を分けることが完了しました。

他にもパスワードリセットやメール認証ができていませんが同じように対応したらいけると思います。
<h3>参考になったサイト</h3>
https://qiita.com/LowSE01/items/ffa256439f665740cc8f

https://leben.mobi/blog/laravel5_multi_login/php/

https://www.willstyle.co.jp/blog/907/

https://takahashi-it.com/php/laravel54-multi-auth/

https://www.larajapan.com/2016/01/18/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E8%AA%8D%E8%A8%BC%EF%BC%88%EF%BC%91%EF%BC%90%EF%BC%89laravel-5-2-%E3%83%9E%E3%83%AB%E3%83%81%E8%AA%8D%E8%A8%BC/