Laravel 8でマルチ認証して一般ログインと管理ログインを分ける方法(UI方式、非Jetstream、非Fortify、非Breeze)

　Laravel 8で一般ログインと管理ログインを分けるために、マルチ認証を実装する方法を紹介します。ここで紹介する方法は従来のUIを用いる方式で、Jetstream、Fortify、Breezeを用いる方式ではありません。

# ゴール

- 一般ログインと管理ログインを分ける。
- Laravel標準のユーザー認証機能(Auth)を使う。
- コードはなるべく共用にする。
- 一般ログインURIは `/login` とする。
- 管理ログインURIは `/login/admin` とし、管理画面は `/admin` 以下とする。
- リセットパスワードができるようにする。

# 前提

- PHP 8の知識がある
- Laravel 8の知識がある、認証機能のあるアプリが作れる
- Laravel 8に必要な環境構築ができる
- Composerが使える状態にある
- [Laravel Installer](https://laravel.com/docs/8.x/installation)が使える状態にある

　Laravelで認証機能のあるアプリを作る知識と環境がある前提でスタートします。今回は一般ユーザー(user)と管理ユーザー(admin)の2種類の認証ができるようにします。3種類以上でも全く同じ方法で拡張できます。
　Windowsで作っているので、MacやLinuxで合わないところは読み替えてください。

# ベースとなるアプリを作る

まずはLaravel 8の `multi-auth` プロジェクトをサクッと作ります。

```bash
$ composer create-project laravel/laravel multi-auth "8.*"
$ cd multi-auth
```

データベースを作って、 `.env` に接続設定します。

```:.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=qiita_multi_auth
DB_USERNAME=dbuser
DB_PASSWORD=password
```

動作することだけ確認しておきます。以下で簡易サーバー起動後、ブラウザで `http://localhost` を確認。

```bash
$ php artisan serve --port=80
```

## npm run dev ができる準備

`npm run dev` できるように、`cross-env` を入れておきます。

```
$ npm install && npm install --save-dev cross-env
```

# Migrationを作る

　Laravel標準で `users` テーブルのMigrationは準備されていますが、今回は管理ユーザーを別テーブルとしたいので `admins` テーブルを作ります。これにより、同じEmailアドレスを使って一般ユーザーと管理ユーザー両方のアカウントを正しく作成できます。

```bash
$ php artisan make:migration create_admins_table
```

　中身は `users` テーブルとほぼ同じです。必要に応じて追加情報を加えても良いでしょう。ソフトデリートにしなくても大丈夫です。

```php:database/migrations/[timestamp]_create_admins_table.php
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->boolean('is_super')->default(false);
            $table->rememberToken();
            $table->timestamps();
            $table->softDeletes();
        });
    }
    [...]
}
```

# テーブルを作る

必要なテーブルのMigration定義はOKなので、早速テーブルを作ります。

```bash
$ php artisan migrate
```

# Modelを作る

　`admins` の Model を作ります。

```bash
$ php artisan make:model Admin
```

　内容は、 `admins` テーブルに合わせて以下のように作ります。

```app/Admin.php
<?php

namespace App\Models;

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

class Admin extends Authenticatable
{
    use Notifiable;

    protected $guard = 'admin';

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

    protected $hidden = [
        'password', 'remember_token',
    ];
}
```

　`Admin` モデルは `Model` クラスではなく `Illuminate\Foundation\Auth\User` クラスを継承します。冒頭で名前を `Authenticatable` としています。また、 `use Notifiable;` でメールを送れるようにしておきます。このあたりの構造は `User` モデルが参考になります。
　`User` モデルとの違いとして、ユーザー認証をデフォルトのユーザーガードではなく `admin` という名前の別ガードにしたいので、 `$guard` を設定しています。

　Laravel 8から、デフォルトの `User` モデルには `use HasFactory` がついてます。今回 `Admin` モデルもこれにならって `HasFactory` も use に入れて実際にFactoryを作るほうが行儀が良いですが、本題から外れるのでここでは省略します。Factoryに関して詳しくは[こちら](https://readouble.com/laravel/8.x/ja/database-testing.html)。

# Guardを作る

　先程設定した `admin` ガードを作ります。ガード追加は `config/auth.php` に定義するだけです。

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

　これで `admin` ガードに `admins` プロバイダーを設定しました。プロバイダーでは、どのモデルを認証に使うべきかなどが設定されます。 `admins` プロバイダーを同じファイルに追加します。今回は `users` プロバイダー同様に同じデータベース上の情報を使うので、同じ設定をモデル名だけ変更して使います。

```php:config/auth.php
    'providers' => [
        [...]
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
    ],
```

# 認証機能のための画面を導入

　Laravel 8ではデフォルトの状態でLaravelの認証関連機能が使える状態になっていますが、認証関連のデフォルト画面などは使える状態になっていません。標準のUIパッケージの中に認証関連のControllerやView(=Blade)が入っているので、まずUIパッケージを入れます。Laravel 8用のUIパッケージバージョンは3なので、これを指定して入れます。

```bash
$ composer require laravel/ui="3.*"
```

　次に認証関連のViewなどをUIのコマンドで作成します。デフォルトで実行すると、ついでに最低限必要なルートやログイン後のホームページ(`HomeController@index`)を作ってくれます。
　もしreactではなくvueを使いたい場合は、コマンド中のreactをvueにして実行します。

```bash
$ php artisan ui react --auth
```

　最後にリソースの取得とコンパイルをします。

```bash
$ npm install && npm run dev
```

ここで `http://localhost/login` を確認すると、ログイン画面が表示されるはずです。

また、トップページ `http://localhost` の右上には以下のようにログインとユーザー登録のリンクが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315589/ba77c206-8133-9abe-9825-261a3259b5ea.png)

# コントローラを修正する

　ログインとユーザー登録では、ユーザーが開けたページのURLからルーター経由でメンバメソッドが呼び出されるため、ガードごとにメソッドを分けることができ、1つのコントローラファイル内で複数のガードの動作を別メソッドとして書くことができます。まずはこの２つを実装します。

## ログインコントローラの修正

```php:app/Http/Controllers/Auth/LoginController.php
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
        $this->middleware('guest:admin')->except('logout');
    }
```

　ログインコントローラに、 `admin` ガードのゲストとしてのアクセスを許可するミドルウェアを登録します。これにより、デフォルトのユーザーログインまたは管理ログインをしている場合に、ゲスト扱いされずこのコントローラへのアクセスを拒否できます。これによって、一般ログインと管理ログインを同時にしている状態を防げます。

　次に、管理ログイン用の動作を追加します。

```php:app/Http/Controllers/Auth/LoginController.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
[...]
    /**
     * 管理者ログイン用
     */
    public function showAdminLoginForm()
    {
        return view('auth.login', ['authgroup' => 'admin']);
    }

    public function adminLogin(Request $request)
    {
        $this->validate($request, [
            'email'   => 'required|email',
            'password' => 'required|min:8'
        ]);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if (Auth::guard('admin')->attempt(['email' => $request->email, 'password' => $request->password], $request->get('remember'))) {
            return redirect()->intended('/admin');
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return back()->withInput($request->only('email', 'remember'));
    }
```

　この2つのメソッドは、 `\vendor\laravel\ui\auth-backend\AuthenticatesUsers.php` の `showLoginForm()` と `login()` メソッドを書き換えたものです。

 フォームの表示をするときに、追加パラメータとして `authgroup` に `admin` を設定して置きます。これにより、管理ユーザー用にフォーム表示する場合にはブレード側で表示内容を変更できるようにします。これにより、ログインフォームの送信先メソッドを、一般ユーザーで使う `login()` ではなく `adminLogin()` にすることを狙っています。

## ユーザー登録コントローラの修正

```php:app\Http\Controllers\Auth\RegisterController.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
[...]
    public function __construct()
    {
        $this->middleware('guest');
        $this->middleware('guest:admin');
    }
```

　ユーザー登録コントローラも `LoginController` 同様にガードコントロールのミドルウェアを登録しておきます。

```php:app\Http\Controllers\Auth\RegisterController.php
use App\Models\Admin;
use Illuminate\Auth\Events\Registered;
[...]
    /**
     * 管理者ログイン用
     */
    protected function adminValidator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    public function showAdminRegisterForm()
    {
        return view('auth.register', ['authgroup' => 'admin']);
    }

    public function registerAdmin(Request $request)
    {
        $this->adminValidator($request->all())->validate();

        event(new Registered($user = $this->createAdmin($request->all())));

        Auth::guard('admin')->login($user);

        if ($response = $this->registeredAdmin($request, $user)) {
            return $response;
        }

        return $request->wantsJson()
                    ? new JsonResponse([], 201)
                    : redirect(route('admin-home'));
    }

    protected function createAdmin(array $data)
    {
        return Admin::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }

    protected function registeredAdmin(Request $request, $user)
    {
        //
    }
```

　ログイン同様、管理ユーザー用の動作を定義しておきます。`adminValidator()` では、メールアドレスの重複チェックを`admins`テーブルで実施するように指定しています。

# 認証機能のための画面を作成

　uiの導入で `resources/views/auth` に関連画面のBladeができたので、一般ログインだけでなく管理ログインにも対応させていきます。

```php:resources\views\auth\login.blade.php
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ isset($authgroup) ? ucwords($authgroup) : ""}} {{ __('Login') }}</div>

                <div class="card-body">
                    @isset($authgroup)
                    <form method="POST" action="{{ url("login/$authgroup") }}">
                    @else
                    <form method="POST" action="{{ route('login') }}">
                    @endisset
                        @csrf
```

　`authgroup` パラメータがある場合、ログイン画面のタイトル表示を変更し、またログインに使う action URL を変更しています。これにより、一般ログインは `/login` 、管理ログインは `/login/admin` にPOSTで要求されることになります。
　登録画面も同様に変更します。

```php:resources\views\auth\register.blade.php
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ isset($authgroup) ? ucwords($authgroup) : ""}} {{ __('Register') }}</div>

                <div class="card-body">
                    @isset($authgroup)
                    <form method="POST" action="{{ url("register/$authgroup") }}">
                    @else
                    <form method="POST" action="{{ route('register') }}">
                    @endisset
                        @csrf
```

　これでコンテンツエリアは `authgroup` に対応しましたが、ヘッダーエリアも対応させるためにレイアウトも編集します。

```php:resources\views\layouts\app.blade.php
                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @if(!Auth::check() && (!isset($authgroup) || !Auth::guard($authgroup)->check()))
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    @isset($authgroup)
                                    <a class="nav-link" href="{{ url("login/$authgroup") }}">{{ __('Login') }}</a>
                                    @else
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                    @endisset
                                </li>
                            @endif

                            @if (Route::has('register'))
                            @isset($authgroup)
                            @if (Route::has("$authgroup-register"))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route("$authgroup-register") }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                            @else
                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                            @endisset
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    @isset($authgroup)
                                    {{ Auth::guard($authgroup)->user()->name }}
                                    @else
                                    {{ Auth::user()->name }}
                                    @endisset
                                </a>
```

# 管理ユーザー用のログイン後画面作成

　デフォルトの `home.blade.php` をコピーして、 `admin.blade.php` を作ります。

```bash
$ cp resources/views/home.blade.php resources/views/admin.blade.php
```

　ちょっと書き換えて、管理者へのメッセージを入れましょう。

```php:resources/views/admin.blade.php
@extends('layouts.app', ['authgroup'=>'admin'])

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">管理者 {{ __('Dashboard') }}</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    You are logged in as 管理者!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

　ここで`@extends`するときに、この画面が`admin`ガードであることをパラメータで指定していますが、これはコントローラ無しでルートから直接このブレードを表示しようとしているため、ここでパラメータ指定しています。

# ルートを作成

　ルートファイルにはすでに `Auth` に必要なルート設定はされていますので、その後ろに管理ユーザー用のルートを追加します。

```php:routes/web.php
Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

// ここから追加
Route::get('/login/admin', [App\Http\Controllers\Auth\LoginController::class, 'showAdminLoginForm']);
Route::get('/register/admin', [App\Http\Controllers\Auth\RegisterController::class, 'showAdminRegisterForm']);

Route::post('/login/admin', [App\Http\Controllers\Auth\LoginController::class, 'adminLogin']);
Route::post('/register/admin', [App\Http\Controllers\Auth\RegisterController::class, 'registerAdmin'])->name('admin-register');

Route::view('/admin', 'admin')->middleware('auth:admin')->name('admin-home');
```

# ログイン後のリダイレクト先設定

　ログイン後、Laravelのデフォルトでは`/home`にリダイレクトされますが、管理ユーザーは`/admin`にリダイレクトさせるようにします。これには認証後リダイレクトのミドルウェアを編集します。

```php:app\Http\Middleware\RedirectIfAuthenticated.php
    public function handle(Request $request, Closure $next, ...$guards)
    {
        $guards = empty($guards) ? [null] : $guards;

        foreach ($guards as $guard) {
            if ($guard == "admin" && Auth::guard($guard)->check()) {
                return redirect(route('admin-home'));
            }
            if (Auth::guard($guard)->check()) {
                return redirect(RouteServiceProvider::HOME);
            }
        }

        return $next($request);
    }
```

　`RedirectIfAuthenticated`ミドルウェアは、`guards`パラメータを受け取るので、これを見て処理を判断しています。

# 認証例外発生時のリダイレクト先設定

　ログインしていない状態で保護されたページを見ようとした場合、今のままではとにかく`/login`にリダイレクトされます。これだと、管理画面にアクセスしようとしたときに一般ログイン画面に飛ばされてしまい、不便です。管理画面用のルートからは`/login/admin`にリダイレクトするようにします。

```php:app\Exceptions\Handler.php
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
[...]
class Handler extends ExceptionHandler
{
    [...] 
    protected function unauthenticated($request, AuthenticationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json(['message' => $exception->getMessage()], 401);
        }
        if ($request->is('admin') || $request->is('admin/*')) {
            return redirect()->guest('/login/admin');
        }

        return redirect()->guest($exception->redirectTo() ?? route('login'));
    }
```

# 動作確認

　ここまでで、一般ユーザーの登録、ログイン、ホーム画面、ログアウト、管理ユーザーの登録、ログイン、ホーム画面、ログアウトができるようになりました。一度簡易サーバーを起動して動作を確認してみてください。

```bash
$ php artisan serve --port=80
```

　残るはリセットパスワードです。

# 追加準備

　`.env` でメールが送信できるように設定しておきます。パスワードリセット用のメールがこの設定を使って送信されます。

```:.env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=--------@gmail.com
MAIL_PASSWORD=---------
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=--------@gmail.com
MAIL_FROM_NAME="ローカルテスト"
```

# パスワードリセット設定を作る

　`config/auth.php` に管理ユーザー用のパスワードリセット設定を作ります。

```php:config/auth.php
    'passwords' => [
        [...]
        'admins' => [
            'provider' => 'admins',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
```

# パスワードリセット関連のコントローラを追加する

　ログインやユーザー登録と違って、パスワードリセットではコントローラのメンバ変数にガードの指定が必要など、1つのコントローラファイル内で処理が難しいので、アプローチを変えてコントローラ自体を別に作ることにします。

## リセットリクエストコントローラの作成

　パスワードリセットリクエスト画面を表示するためのコントローラである`ResetPasswordController`をコピーします。

```bash
$ cp app/Http/Controllers/Auth/ResetPasswordController.php app/Http/Controllers/Auth/AdminResetPasswordController.php
```

```php:app\Http\Controllers\Auth\AdminResetPasswordController.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
[...]
class AdminResetPasswordController extends Controller
{
    [...]
    protected $redirectTo = '/admin';

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

    public function showResetForm(Request $request, $token = null)
    {
        return view('auth.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email, 'authgroup' => 'admin']
        );
    }

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

    protected function broker()
    {
        return Password::broker('admins');
    }
```

　`guard()` をオーバーライドして`admin`ガードを指定し、`broker()`をオーバーライドして`config/auth.php`に設定したパスワードリセット設定をブローカーに指定します。あとはログインコントローラのときと同様です。メソッド名ではなく、クラス名そのものが変わっていることに注意してください。

## リセット実施コントローラの作成

　同様に、パスワードリセット実施画面を表示するためのコントローラである`ForgotPasswordController`をコピーし、`broker()`をオーバーライドします。

```bash
$ cp app/Http/Controllers/Auth/ForgotPasswordController.php app/Http/Controllers/Auth/AdminForgotPasswordController.php
```

```php:app\Http\Controllers\Auth\AdminForgotPasswordController.php
use Illuminate\Support\Facades\Password;
[...]
class AdminForgotPasswordController extends Controller
{
    [...]
    use SendsPasswordResetEmails;

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

    public function showLinkRequestForm()
    {
        return view('auth.passwords.email')->with(
            ['authgroup' => 'admin']
        );
    }

    protected function broker()
    {
        return Password::broker('admins');
    }
}
```

# パスワードリセットリクエスト画面修正

　パスワードリセットリクエスト画面でも、画面上の表示とフォームの`action`をガードによって変更するように修正します。

```php:resources\views\auth\passwords\email.blade.php
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ isset($authgroup) ? ucwords($authgroup) : ""}} {{ __('Reset Password') }}</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    @isset($authgroup)
                    <form method="POST" action="{{ route("$authgroup.password.email") }}">
                    @else
                    <form method="POST" action="{{ route('password.email') }}">
                    @endisset
                        @csrf
```

# パスワードリセット実施画面修正

　これも同様に変更します。

```php:resources\views\auth\passwords\reset.blade.php
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ isset($authgroup) ? ucwords($authgroup) : ""}} {{ __('Reset Password') }}</div>

                <div class="card-body">
                    @isset($authgroup)
                    <form method="POST" action="{{ route($authgroup.'.password.update') }}">
                    @else
                    <form method="POST" action="{{ route('password.update') }}">
                    @endisset
                        @csrf
```

# ログイン画面修正

　「パスワードを忘れた」のリンク先を制御するため、ログイン画面を修正します。

```php:resources\views\auth\login.blade.php
                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>

                                @if (Route::has(isset($authgroup) ? $authgroup.'.password.request' : 'password.request'))
                                    <a class="btn btn-link" href="{{ route(isset($authgroup) ? $authgroup.'.password.request' : 'password.request') }}">
                                        {{ __('Forgot Your Password?') }}
                                    </a>
                                @endif
                            </div>
                        </div>
```

# リセットパスワードメールを作成

　リセットパスワードのメールには、パスワードリセットのためのリンクボタンが含まれます。このリンクを変更するには、このリンクを含む管理ユーザー用のパスワードリセット通知を新しく作って、管理ユーザーのリセットの場合にはそれを使うように設定する必要があります。

## 管理ユーザー用パスワードリセット通知の作成

```bash
$ php artisan make:notification AdminResetPassword
```

実際の中身は、`Illuminate\Auth\Notifications\ResetPassword`を継承して以下のように `toMail()` と `buildMailMessage()`を親からコピーしてきて少し改造します。

```php:app\Notifications\AdminResetPassword.php
<?php

namespace App\Notifications;

use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Lang;

class AdminResetPassword extends Resetpassword
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($token)
    {
        $this->token = $token;
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $this->token);
        }

        if (static::$createUrlCallback) {
            $url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
        } else {
            $url = url(route('admin.password.reset', [
                'token' => $this->token,
                'email' => $notifiable->getEmailForPasswordReset(),
            ], false));
        }

        return $this->buildMailMessage($url);
    }

    protected function buildMailMessage($url)
    {
        return (new MailMessage)
            ->subject(Lang::get('Reset Admin Password Notification'))
            ->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
            ->action(Lang::get('Reset Password'), $url)
            ->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]))
            ->line(Lang::get('If you did not request a password reset, no further action is required.'));
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}
```

## 管理ユーザー用パスワードリセット通知をモデルに設定

　パスワードリセットに使われる通知は、`Authenticatable`なユーザーモデルに設定されています。デフォルトでは`Illuminate\Auth\Notifications\ResetPassword`ですが、これを`app/Models/Admin`モデルでは上で作った通知に変更します。

```php:app\Models\Admin.php
use App\Notifications\AdminResetPassword;
[...]
class Admin extends Authenticatable
{
    [...]
    // Override default reset password
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new AdminResetPassword($token));
    }
```

# パスワードリセット用ルートを作成

　ルートファイルに追記します。

```php:routes/web.php
Route::get('password/admin/reset', [App\Http\Controllers\Auth\AdminForgotPasswordController::class, 'showLinkRequestForm'])->name('admin.password.request');
Route::post('password/admin/email', [App\Http\Controllers\Auth\AdminForgotPasswordController::class, 'sendResetLinkEmail'])->name('admin.password.email');
Route::get('password/admin/reset/{token}', [App\Http\Controllers\Auth\AdminResetPasswordController::class, 'showResetForm'])->name('admin.password.reset');
Route::post('password/admin/reset', [App\Http\Controllers\Auth\AdminResetPasswordController::class, 'reset'])->name('admin.password.update');
```

以上です！お疲れ様でした。
※元ネタは [Laravel 6版](https://qiita.com/hitotch/items/8447d9c60fa350b7c366)。