Laravel8よりLaravelの認証機能を利用する際に大きく分けてJetStream、Laravel BreezeとLaravel UIを利用した3つの方法があります。JetStreamはさらにInertia.js、Livewire、BreezeにはVue、React、Alpine.jsなどフロントエンド側で利用する技術が異なります。JetStream, Breezeどちらでも特別な設定を行うことなく認証機能を実装することができます。しかし1つのLaravelアプリケーションの中でマルチ認証を実装したい場合は追加での設定が必要となりMiddlewareやGuardなどの知識が必要となります。すべての組み合わせでマルチ認証の設定方法について説明をすることは難しいので本文書ではLaravel BreezeとInertia.js(Vue)を利用した方法での説明を行っています。パスワードリセットの方法も含まれています。それ以外の他の技術を使った場合はどうするのか?と不安になる人もいるかもしれませんがLaravelの内部で行われる認証機能は共通なので1つの技術での設定方法がわかれば他の技術での設定は難しくありません。

Inertia.js, Vue, React, Alpine.jsはJavaScript、LivewireのみPHPを利用する技術です。
fukidashi

マルチ認証とは

例えばECサイトを構築する際には2つのシステムが必要となります。一つは購入履歴、配送先、支払い方法などユーザが利用するシステムでもう一つは商品登録、配送料の設定やユーザ全員の購入履歴などシステム全般を管理するために利用するシステムです。1つのLaravelアプリケーションの中に2つのシステムを構築し独立した2つの認証機能を持たせる場合にマルチ認証と呼びます。

Laravelはデフォルトでは1つの認証システムしか利用することができませんが複数の認証が設定できるように作られているためLaravelの基本的な動作を理解することができればマルチ認証のアプリケーションを構築することができます。特にLaravelの中の機能の一つであるGuardを理解しておくことが重要になります。

ECサイトを構築する場合に必ず2つのシステムが必要なわけではありません。ユーザが利用するシステムがない場合は購入する度に配送先など購入に必要な情報を毎回すべて入力する必要があります。システム全般を管理するシステムがない場合は商品の登録、配送料金の設定など変更が必要な場合は直接ファイルを更新する必要があります。
fukidashi

マルチ認証を行う環境について

冒頭で説明した通りLaravel8ではさまざまなフロントエンドの技術が利用されているのでマルチ認証の設定方法は異なります。しかしフロントエンド部分の技術の選択肢が広がってもLaravelのメイン部分であるバックエンドは同じであるためいずれかの技術での設定方法がわかれば別の方法の設定を比較的簡単に行うことができます。本文書ではLaravel Breeze+Inertia.js(Vue.js)を利用する場合とLaravel Breezeのデフォルト(alpine.js)の場合で設定を行います。

Alpine.jsはVue.jsやReactのようにフロントエンドに関するページ全体を作成するために利用するものではなく既存のHTMLにドロップダウンメニューなどJavaScriptを利用したシンプルなインタラクティブな処理を行いたい時に利用します。Breezeではログアウトを表示するドロップダウンメニューで利用しています。Alpine.jsを利用している範囲は少ないためAlpine.jsの知識はなくてもLaravel Breezeは利用できます。
fukidashi

Laravelプロジェクトの作成

Laravelの環境を構築するためには事前にPHPのcomposerとJavaScriptのnodeのインストールを完了しておく必要があります。

Laravelプロジェクトの作成はcomposerまたはlaravel newコマンドを利用して行います。本文書ではlaravel_breeze_multi_authというプロジェクト名をつけていますが任意の名前をつけてください。


 % laravel new laravel_breeze_multi_auth

composerコマンドでもプロジェクトの作成を行うことができます。


$ composer create-project --prefer-dist laravel/laravel laravel_breeze_multi_auth 

作成したプロジェクトに移動してphp artisanコマンドでLaravelのバージョンを確認します。本文書では8.62.0バージョンで動作確認を行っています。


 % php artisan -V
Laravel Framework 8.62.0

Laravel Breezeを利用するためにはcomposerコマンドを利用してパッケージのインストールを行う必要があります。


 % composer require laravel/breeze --dev

パッケージのインストール後にbreezeのインストールを行います。インストール時にデフォルト(Alpine.js)、Inertia.js + Vue.js, Inertia.js + Reactのどれを利用するのかオプションで指定することができます。デフォルト(Alpine.js)を利用する際はオプジョンに何もつけずに下記のコマンドを実行します。


 % php artisan breeze:install

php artisan breeze:installを実行すると認証に関連するルーティングの追加、ユーザ登録、ログイン処理が記述されたコントローラーファイル、ユーザ登録、ログイン画面に関するファイルが作成されます。

デフォルトとInertia.jsではバックエンドのLaravelが利用しているルーティングやコントローラーなどの内容は同じです。ユーザ登録やログイン画面などPHPが利用されていないHTMLとJavaScriptに関連するフロントエンド側で利用するファイルが異なります。デフォルトではbladeファイル、Inertia.jsではvueファイルが利用されます。
fukidashi

Inertia.js + Vueをインストールする場合は下記のようにvueを指定します。


 % php artisan breeze:install vue

Inertia.jsでReactを利用したい場合はオプジョンにreactを指定します。


 % php artisan breeze:install react

breezeのインストールが完了したらnpm install && npm run devコマンドを実行してJavaScriptのパッケージとビルドを行います。


 % npm install && npm run dev

ビルドが完了後にphp artisan serveコマンドでLaravelの開発サーバの起動を行います。Breezeのインストールが完了しているので右上にはLogin inとRegisterのリンクが表示されます。


 % php artisan serve
Laravel8のトップページ
Laravel8のトップページ

データベースの設定

Laravelプロジェクトの作成とBreezeのインストールが完了するとユーザの登録画面が表示されますがデータベースがないためユーザの登録を行うことができません。ユーザの登録が行えるようにデータベースの作成を行います。簡易的に利用することができるSQLiteデータベースを利用します。SQLiteではデータベースにファイルを利用するためtouchコマンドでdatabaseフォルダの下にdatabase.sqliteファイルを作成します。


 % touch database/database.sqlite

LaravelからSQLiteデータベースに接続するために環境変数を含む.envファイルの更新します。.envファイルには先頭にDB_のついている環境変数が複数設定されていますが、DB_CONNECTIONを残してDB_から始まる変数を削除します。DB_CONNECTIONにはデフォルトでmysqlが設定されていますがsqliteに変更します。


DB_CONNECTION=sqlite

.envファイルを更新後、データベースにテーブルを作成するためにphp artisan migrateコマンドを実行します。


 % php artisan migrate

マルチ認証の共通設定

Breezeのデフォルト、Inartia.js+Vueでのマルチ認証の設定はフロントエンド部分の設定を除いて同じなので共通設定については一緒に説明を行います。

モデルファイルの作成

マルチ認証を利用する場合は認証によってユーザ情報を保存するテーブルを分けます。デフォルトの認証ではusersテーブルを利用します。新たに認証に利用するテーブルを作成しますがテーブルの名前についてはマルチ認証を利用するアプリケーションによって異なります。本文書ではadminsテーブルを利用してモデルファイルにはAdmin.phpを利用します。モデルファイルの作成にはphp artisan make:modelコマンドを利用し-mオプションをつけます。-mオプションでマイグレーションファイルも同時に作成されます。


 % php artisan make:model Admin -m
Model created successfully.
Created Migration: 2021_10_01_141409_create_admins_table

作成されたAdmin.phpファイルはUser.phpをコピーして貼り付けます。コピー後にクラス名はAdminにして保存してください。


namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

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

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

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

作成されたマイグレーションファイルもusersテーブル作成用のマイグレーションファイルの列情報をコピーして利用します。


public function up()
{
    Schema::create('admins', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}

php artisan make:migrateコマンドを実行してadminsテーブルを作成します。


 % php artisan migrate

Guardの設定

Laravelは認証の仕組みとしてGuardを利用します。名前の通りLaravelにアクセスするリクエストに対してガードする(守る)役目を持っています。 デフォルトではブラウザからのアクセス用のwebとAPI用のapiが設定されています。ルーティングにweb guardを設定している場合はログイン画面からのユーザの認証が行われていないとweb guardを設定した場所にアクセスすることができません。

Guardは追加を行うことができるので追加認証機能のために新たにadmin guardの追加を行います。設定はconfig/auth.phpファイルで行います。Guardがどのような処理を行っているのかわからなくても追加したadminの内容を見るとモデルApp\Models\Admin 、パスワード、adminsテーブルに関連していることがわかります。


//中略

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],

//中略

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

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

//中略

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

ルーティングの設定

breeze:installを実行するとルーティングのweb.phpファイルにはログイン後に表示されるダッシュボードに関するルーティングと認証に関するルーティングが追加されます。認証に関してはauth.phpファイルに記述されています。


Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

ダッシュボードの設定については共通ではないので後ほど説明します。
fukidashi

auth.phpファイルによってどのようなルーティングが追加されているかはphp artisan route:listコマンドを実行することがで確認することができます。/login, /registerなど認証に関するルーティングが追加されていることを確認してください。

デフォルトのルーティング
デフォルトのルーティング

追加するadmin認証についてのルーティングを追加するためにroutes/auth.phpファイルを複製して/routes/admin.phpファイルを作成します。

作成したadmin.phpでは名前空間の設定変更とルーティング毎に設定されているmiddlewareにパラメータadminを追加します。

この後認証に関連するコントローラーファイルをapp\HTTP\Controllersの下のAdminの下に保存するのでインポートするクラスのパスを下記のように変更します。Authの前にAdminを追加します。


use App\Http\Controllers\Admin\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Admin\Auth\ConfirmablePasswordController;
use App\Http\Controllers\Admin\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\Admin\Auth\EmailVerificationPromptController;
use App\Http\Controllers\Admin\Auth\NewPasswordController;
use App\Http\Controllers\Admin\Auth\PasswordResetLinkController;
use App\Http\Controllers\Admin\Auth\RegisteredUserController;
use App\Http\Controllers\Admin\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route;

admin.phpファイルのルーティングにはmiddlewareのguestとauthが設定されていますがguestとauthにパラメータadminを追加します。


Route::get('/register', [RegisteredUserController::class, 'create'])
                ->middleware('guest:admin')
                ->name('register');

Route::post('/register', [RegisteredUserController::class, 'store'])
                ->middleware('guest:admin');

Route::get('/login', [AuthenticatedSessionController::class, 'create'])
                ->middleware('guest:admin')
                ->name('login');

Route::post('/login', [AuthenticatedSessionController::class, 'store'])
                ->middleware('guest:admin');

Route::get('/forgot-password', [PasswordResetLinkController::class, 'create'])
                ->middleware('guest:admin')
                ->name('password.request');

Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
                ->middleware('guest:admin')
                ->name('password.email');

Route::get('/reset-password/{token}', [NewPasswordController::class, 'create'])
                ->middleware('guest:admin')
                ->name('password.reset');

Route::post('/reset-password', [NewPasswordController::class, 'store'])
                ->middleware('guest:admin')
                ->name('password.update');

Route::get('/verify-email', [EmailVerificationPromptController::class, '__invoke'])
                ->middleware('auth:admin')
                ->name('verification.notice');

Route::get('/verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
                ->middleware(['auth', 'signed', 'throttle:6,1'])
                ->name('verification.verify');

Route::post('/email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
                ->middleware(['auth', 'throttle:6,1'])
                ->name('verification.send');

Route::get('/confirm-password', [ConfirmablePasswordController::class, 'show'])
                ->middleware('auth:admin')
                ->name('password.confirm');

Route::post('/confirm-password', [ConfirmablePasswordController::class, 'store'])
                ->middleware('auth:admin');

Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
                ->middleware('auth:admin')
                ->name('logout');

作成したadmin.phpファイルをauth.phpファイルと同じ方法でweb.phpファイルに追加します。


require __DIR__.'/auth.php';

require __DIR__.'/admin.php';

php artisan route:listコマンドを実行しても追加したルーティング(admin.php)に関するコントローラーが存在しないためエラーになります。


 % php artisan route:list

   Illuminate\Contracts\Container\BindingResolutionException 

  Target class [App\Http\Controllers\Admin\Auth\RegisteredUserController] does not exis

コントローラーの作成

追加のadmin認証に関連するコントローラーファイルを保存するためにapp¥HTTP¥ControllersにAdminフォルダを作成します。Adminフォルダの下にbreeze:install実行後に作成されたapp¥HTTP¥Controllers¥Authフォルダをそのままコピーします。


 % mkdir Admin
 % cp -r Auth Admin

Authフォルダの下には8つのコントロールファイルがありますがすべてのファイルを開いて名前空間の変更を行います。Authの名前の前にAdminを挿入してください。変更はファイルをAdminフォルダにコピーしてパスが変わったためです。


namespace App\Http\Controllers\Admin\Auth;

8つのファイルの名前空間を変更すると先ほど失敗したphp artisan route:listコマンドを実行することができます。


 % php artisan route:list

しかし、ルーティングは追加されておらずAction, Middleware列が追加したadmin.phpファイルの内容に上書きされていることがわかります。理由はroutes/auth.phpとadmin.phpファイルのルーティング先の設定が同じためです。auth.phpとadmin.phpでルーティング先の/registerが2回登録され後から追加したadmin.phpファイルのルーティングによってauth.phpファイルのルーティングが上書きされています。上書きされるのは/registerだけでなく/loginなどauth.phpに記述されているすべてのルーティングです。

admin.php追加後のルーティング
admin.php追加後のルーティング

ルーティングの重複を避けるためデフォルト認証のルーティングの/registerを追加するadmin認証では/admin/registerとします。そのためにadmin.phpファイルのルーティングにprefixで一括で/adminを追加します。URLだけではなくnameも区別するためにname列にnameメソッドによりはadmin.を先頭につけます。


Route::prefix('admin')->name('admin.')->group(function(){
    require __DIR__.'/admin.php';
});

設定後にphp artisan route:listを実行するとURI列にadmin/、Name列にadmin.が追加されたルーティングが表示されます。

追加されたルーティングの一部
追加されたルーティングの一部
画像には表示されていませんがURIのlogin, registerのルーティングも上記とは別に表示されます。この設定によりauth.php, admin.phpに記述されたルーティングを別々のルーティングとして登録することができます。
fukidashi

ここまでの設定が完了するとブラウザで/admin/register, /admin/loginにアクセスするとユーザ登録画面とログイン画面が表示され/admin/register画面からユーザを登録することができます。しかし、登録したユーザが保存されるテーブルはadminsではなくusersなのでマルチ認証の設定は完了していません。

ユーザ登録、ログイン画面の設定

追加したadmin認証でユーザを正しく登録するためにはフロント画面の設定を変更する必要があり、フロント画面に利用されるファイルがBreezeのデフォルトとIneartia.js+Vue.jsでは異なります。変更するファイルは異なりますが行うことは同じでPOSTリクエスト送信先の設定、ページ内リンク先の設定、ダッシュボードページの作成です。設定変更が異なるの別々に説明を行っていきます。

デフォルトの場合(Alpine.js)

admin.phpで追加したルーティングを確認すると/admin/registerにアクセスするとapp¥Http¥Controller¥Admin¥Auth¥RegisteredUserController.phpファイルのcreateメソッドが実行されます。


public function create()
{
    return view('auth.register');
}

view関数に設定されているのはresouces¥views¥authの下に保存されているregister.blade.phpファイルです。register.blade.phpファイルにはユーザ登録のフォームがHTMLによって記述されていますが送信先がregisterになっているのでadmin.registerに変更する必要があります。


<form method="POST" action="{{ route('register') }}">

register.blade.phpファイルを変更するとデフォルトの認証の送信先もadmin.registerになるため新たにresourdces¥viewsフォルダにadminフォルダを作成しauthフォルダの中身をすべてコピーします。

コピーしたadmin¥auth¥register.blade.phpファイルを開いてPOSTリクエストの送信先をadmin.registerに変更します。


<form method="POST" action="{{ route('admin.register') }}">

register.blade.phpファイルではroute関数を利用して他のページへのリンクも設定されているので変更を行います。loginからadmin.loginに変更します。


<div class="flex items-center justify-end mt-4">
    <a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('admin.login') }}">
        {{ __('Already registered?') }}
    </a>

    <x-button class="ml-4">
        {{ __('Register') }}
    </x-button>
</div>

register.blade.phpファイルだけではなくログインに利用するadmin¥auth¥login.blade.phpファイルのPOSTリクエストの送信先とリンクの設定を変更します。


<form method="POST" action="{{ route('admin.login') }}">

<div class="flex items-center justify-end mt-4">
    @if (Route::has('admin.password.request'))
        <a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('admin.password.request') }}">
            {{ __('Forgot your password?') }}
        </a>
    @endif

    <x-button class="ml-3">
        {{ __('Log in') }}
    </x-button>
</div>

admin¥authの下に保存されているその他のBladeファイルconfirm-password.blade.php, forgot-password.blade.php, reset-password.blade.php, verify-email.blade.phpについてもPOSTリクエストの送信先やリンクの設定を更新を行ってください。

register.blade.phpファイルの更新が完了したらRegisteredUserController.phpファイルのcreateメソッドのview関数で指定するbladeファイルを変更します。


public function create()
{
    return view('admin.auth.register');
}

この設定により/admin/registerにアクセスするとadmin.auth.registerで指定されているbladeファイルの内容が表示されます。

ログイン処理に関連するAuthenticatedSessionController.phpファイルでもcreateメソッドを更新します。


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

その他のConfirmablePasswordController.php, EmailVerificationPromptController.php, NewPasswordController.php, PasswordResetLinkController.phpファイルでもview関数の値を更新します。


public function show()
{
    return view('admin.auth.confirm-password');
}

public function __invoke(Request $request)
{
    return $request->user()->hasVerifiedEmail()
                ? redirect()->intended(RouteServiceProvider::HOME)
                : view('admin.auth.verify-email');
}

public function create(Request $request)
{
    return view('admin.auth.reset-password', ['request' => $request]);
}

public function create()
{
    return view('admin.auth.forgot-password');
}

Inertia.js + Vue.jsの場合

admin.phpで追加したルーティングを確認すると/admin/registerにアクセスするとapp¥Http¥Controller¥Admin¥Auth¥RegisteredUserController.phpファイルのcreateメソッドが実行されます。


public function create()
{
    return Inertia::render('Auth/Register');
}

render関数の中にあるAuth/Registerは¥resources¥js¥Pagesの下に保存されているAuth¥Register.vueファイルです。ファイルを開いてscriptタグ側にあるsubmitメソッドを確認してください。postリクエストの送信先がregisterになっていることがわかります。


methods: {
    submit() {
        this.form.post(this.route('register'), {
            onFinish: () => this.form.reset('password', 'password_confirmation'),
        })
    }
}

これはデフォルト認証が利用するルーティングなのでregisterではなくadmin.registerに変更することが必要となります。Auth¥Register.vueファイルはデフォルトの認証で利用するため更新することはできません。そのためPagesフォルダに新たにAdminフォルダを作成しAuthフォルダをAdminフォルダの下にコピーします。


% mkdir Admin
% cp -r Auth Admin 
// resources¥js¥Pagesフォルダで実行

Admin¥Authの下にコピーしたRegister.vueファイルを開いてsubmitメソッドを変更します。


methods: {
    submit() {
        this.form.post(this.route('admin.register'), {
            onFinish: () => this.form.reset('password', 'password_confirmation'),
        })
    }
}

POSTリクエストの送信先とroute関数の引数に設定されている値も変更を行います。loginからadmin.loginに変更しています。loginからadmin.logingへの変更はルーティングのname列の値に対応し/registerから/admin/registerに変更したことを意味します。


<div class="flex items-center justify-end mt-4">
    <Link
        :href="route('admin.login')"
        class="underline text-sm text-gray-600 hover:text-gray-900"
    >
        Already registered?
    </Link>

Admin¥Auth¥Login.vueファイルのPOSTリエクストの送信先とroute関数の引数を変更します。


methods: {
    submit() {
        this.form.post(this.route('admin.login'), {
            onFinish: () => this.form.reset('password'),
        })
    }
}

<div class="flex items-center justify-end mt-4">
    <Link
        v-if="canResetPassword"
        :href="route('admin.password.request')"
        class="underline text-sm text-gray-600 hover:text-gray-900"
    >
        Forgot your password?
    </Link>

その他のConfirmPassword.vue, ForgotPassword.vue, ResetPasswod.vue, VerifyEmail.vueファイルも同様にPOSTリクエスト先とroute関数の設定がある場合は引数の値を変更します。

Vueファイルを更新したら更新内容を反映させるためにビルドが必要になります。npm run watchコマンドを実行するとvueファイルの更新を検知しビルドを自動で行います。


% npm run watch

vueファイルの変更が完了したら再度RegisteredUserController.phpファイルを確認します。createメソッドのrenderメソッドはAuth/Registerになっていますが先ほどAdmin/Auth/Registerを作成したので変更を行います。


public function create()
{
    return Inertia::render('Admin/Auth/Register');
}

/admin/registerにブラウザからアクセスするとAdmin/Auth/Registerファイルの内容が表示されることになります。 Admin¥Authフォルダに保存されているコントロラーファイルすべてを確認してrender関数の値を変更します。

ログイン処理に関係するAuthenticatedSessionController.phpでもcreateメソッドのrender関数の値をAuth/LoginからAdmin/Auth/Loginに変更します。


public function create()
{
    return Inertia::render('Admin/Auth/Login', [
        'canResetPassword' => Route::has('password.request'),
        'status' => session('status'),
    ]);
}

その他のConfirmablePasswordController.php, EmailVerificationPromptController.php, NewPasswordController.php, PasswordResetLinkController.phpファイルでもrender関数の値を更新します。


public function show()
{
    return Inertia::render('Admin/Auth/ConfirmPassword');
}

public function __invoke(Request $request)
{
    return $request->user()->hasVerifiedEmail()
                ? redirect()->intended(RouteServiceProvider::HOME)
                : Inertia::render('Admin/Auth/VerifyEmail', ['status' => session('status')]);
}

public function create(Request $request)
{
    return Inertia::render('Admin/Auth/ResetPassword', [
        'email' => $request->email,
        'token' => $request->route('token'),
    ]);
}

public function create()
{
    return Inertia::render('Admin/Auth/ForgotPassword', [
        'status' => session('status'),
    ]);
}

ユーザの登録

ここから再度共通の設定を行っていきます。

ユーザ登録画面の表示では、Admin¥RegisteredUserController.phpファイルのcreateメソッドに注目していましたがユーザの登録を行う際はstoreメソッドに注目します。

storeメソッドはUserモデルが利用されているためUserからAdminモデルへ変更を行います。またemailのバリデーションでusersテーブルにunique設定が行われているのでusersからadminsに変更します。


//略
use App\Models\Admin;
//略
public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:admins',
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
        ]);

        $user = Admin::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);
//略

ファイルの更新が完了したらブラウザで/admin/registerにアクセスします。ファイルが更新されているか確認するためにAlready registgeredにカーソルを当ててリンク先が/admin/loginになっているか確認してください。もし/loginのままの場合は正しいファイルが更新されていないかVueの場合はJavaScriptファイルのキャッシュがクリアされていない可能性があるのでブラウザのキャッシュのクリアを行ってください。

リンクの確認
リンクの確認

adminsテーブルにユーザが登録できるかどうか確認するために一度データベースをリフレッシュします。


% php artisan migrate:refresh

ユーザ登録画面からユーザの登録を行いadminsテーブルにユーザ情報が追加されるか確認を行ってください。本文書ではデータベースのGUIの管理ソフトウェアであるTablePlusを利用していています。

adminsテーブルへのユーザ登録の確認
adminsテーブルへのユーザ登録の確認

/admin/registerのユーザ登録画面からユーザを登録するとadminsテーブルへのユーザ登録が完了してもここまでの設定では登録後にリダイレクト(/loginへ)が行われブラウザ上にはログイン画面が表示されダッシュボードは表示されません。

ダッシュボードへのリダイレクト

RegisteredUserController.phpでのユーザ登録後の処理を確認します。redirect関数の引数にRouteServiceProvider.phpで設定されているHOMEが指定されています。これはユーザ登録後にHOMEに指定されているURLにリダイレクトされることになります。


return redirect(RouteServiceProvider::HOME);

RegisteredUserController.phpを開いてHOMEの値を確認します。HOMEには/dashboardが設定されています。


class RouteServiceProvider extends ServiceProvider
{
    public const HOME = '/dashboard'
//略

HOMEには/dashboardが設定さているためユーザの作成が完了すると/dashboardにリダイレクトされます。/dashboardはデフォルトのユーザ用のダッシュボードなので新たにadmin認証用のダッシュボードを設定する必要があります。

/dashboardにリダイレクトされるとデフォルト認証のチェックが行われusersテーブルにユーザは登録されていないため認証に失敗して/loginにリダイレクトされログイン画面が表示されます。
fukidashi

RouteServiceProvider.phpファイルにADMIN_HOMEを追加します。ADMIN_HOMEには/admin/dashboardを設定します。


public const ADMIN_HOME = '/admin/dashboard';

ADMIN_HOMEを追加したらRegisteredUserController.phpのリダイレクト先をHOMEからADMIN_HOMEに変更します。これでユーザの登録が完了したらADMIN_HOMEの/admin/dashboardにリダレクトすることになります。


return redirect(RouteServiceProvider::ADMIN_HOME);

ダッシューボードページの作成

ダッシュボードページはフロントエンド側のページなのでBreezeのデフォルトとinertia.js + Vue.jsで更新する内容が異なります。別々に説明を行っています。

デフォルトの場合

web.phpファイルには/admin/dashboardのルーティングが存在しないので/dashboardを参考に新たに追加します。view関数の値はdashboardからadmin.dashboardに変更します。middlewareのauthのパラメータに:adminも追加しています。


Route::prefix('admin')->name('admin.')->group(function(){

    Route::get('/dashboard', function () {
        return view('admin.dashboard');
    })->middleware(['auth:admin'])->name('dashboard');
    
    require __DIR__.'/admin.php';
});

view関数で指定したadmin.dashboardを作成するためにviewsフォルダにあるdashboard.blade.phpファイルをadminフォルダの下にコピーします。

dashboard.blade.phpファイルではレイアウトファイルにlayoutsフォルダにあるapp.blade.phpファイルを利用しています。追加のadmin認証では異なるレイアウトファイルを利用するためにapp.blade.phpファイルをコピーしてlayoutsフォルダの下にadmin.blade.phpを作成します。


<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    You're logged in!
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

admin.blade.phpファイルを作成後にdashboard.blade.phpを参考にx-app-layoutタグの名前をx-admin-layoutに変更しますがこれだけではレイアウトのadmin.blade.phpの内容を表示させることはできません。app¥ViewのフォルダにあるAppLayout.phpファイルをコピーしてAdminlayout.phpファイルを作成してください。作成したAdminlayout.phpファイルのクラス名とview関数の値を作成したlayouts.adminに変更します。


namespace App\View\Components;

use Illuminate\View\Component;

class AdminLayout extends Component
{
    /**
     * Get the view / contents that represents the component.
     *
     * @return \Illuminate\View\View
     */
    public function render()
    {
        return view('layouts.admin');
    }
}

admin.blade.phpファイルではナビゲーションメニューのためにlayoutsフォルダのnavigation.blade.phpファイルを利用しています。


@include('layouts.navigation')

navigation.blade.phpファイルではダッシュボード画面に表示されるリンクの設定が記述されておりデフォルト認証に関係するルーティング先が設定されているのでadmin認証のリンク先に変更する必要があります。navigation.blade.phpファイルをコピーしてadmin_navigation.blade.phpを作成します。

route関数の引数とdashboardの場合はadmin.dashboard, logoutの場合はadmin.logoutといったようにadmin認証のルーティングになるようにadmin.を追加します。Navigation Linksの箇所についてはroute関数だけではなくrouteIs関数の値も変更します。


<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <x-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
        {{ __('Dashboard') }}
    </x-nav-link>
</div>

admin_navigation.blade.phpの更新が完了したらadmin.blade.phpの@includeの引数をadmin_navigationに変更します。


@include('layouts.admin_navigation')

デフォルトとadmin認証用のどちらのダッシュボードが表示されているのかすぐに区別できるように”You’re logged in!”を”You’re Admin User!”に変更します

Inertia.js + Vue.jsの場合

web.phpファイルには/admin/dashboardのルーティングが存在しないので/dashboardを参考に新たに追加します。render関数で指定されているDashboardファイルもadmin用にAdmin/Dashboardに変更しています。middlewareのauthにもパラメータのadminを追加しています。


Route::prefix('admin')->name('admin.')->group(function(){

  Route::get('/dashboard', function () {
      return Inertia::render('Admin/Dashboard');
  })->middleware(['auth:admin', 'verified'])->name('dashboard');

require __DIR__.'/admin.php';
});

render関数で指定したAdmin¥Dashboardを作成します。PagesフォルダにあるDashboard.vueをPages¥Adminフォルダにコピーします。

Dashboard.vueファイルの中ではページのレイアウトとしてAuthenticatedコンポーネントを利用しています。Authenticatedコンポーネント内のリンク先はすべてデフォルト認証用のルーティングが設定されているのでresources¥js¥Layoutsフォルダ下にあるAuthenticated.vueファイルをコピーしてAdminAuthenticated.vueファイルを作成します。

作成したAdminAuthenticated.vueファイルではリンク先にadmin用のルーティングを指定するためroute関数の中に設定している値にadmin.をつけています。特にログアウトのroute関数は忘れずに変更を行ってください。


<div class="mt-3 space-y-1">
    <BreezeResponsiveNavLink
        :href="route('admin.logout')"
        method="post"
        as="button"
    >
        Log Out
    </BreezeResponsiveNavLink>
</div>

<BreezeNavLink
    :href="route('admin.dashboard')"
    :active="route().current('admin.dashboard')"
>
    Dashboard
</BreezeNavLink>

AdminAuthenticated.vueファイルの更新が完了したらPages¥Admin¥Dashboard.vueファイルでレイアウトのコンポーネントであるAuthenticatedをAdminAuthenticatedに変更します。


import BreezeAuthenticatedLayout from "@/Layouts/AdminAuthenticated.vue";

デフォルトとadmin認証用のどちらのDashboard.vueファイルが表示されているのかすぐに区別できるように”You’re logged in!”を”You’re Admin User!”に変更します。

リダイレクトの確認

ユーザ登録後のリダイレクト

追加したadmin認証のダッシュボードページが作成できたら/admin/registerからユーザの登録を行います。ユーザを登録する前にデータベースのリフレッシュを行っておきます。


% php artisan migrate:refresh

ブラウザで/admin/registerにアクセスしてユーザの登録を行います。しかし先ほどと変わらずログイン画面が表示されます。

再度ユーザ登録後の処理を確認するためRegisteredUserController.phpファイルを確認します。ユーザ作成後にlogin処理を行う部分でguardにadminを設定します。


// Auth::login($user) 変更前
Auth::guard('admin')->login($user);

/admin/registerからユーザを登録するとようやくダッシュボードが表示されます。

ダッシュボードの表示
ダッシュボードの表示

ログイン後のリダイレクト

admin認証でログインした状態で/admin/loginや/admin/registerにアクセスを行うと/login画面にリダイレクトされます。ログイン中の場合は/admin/dashboardへリダイレクトされるようにmiddewareのguestに対応するRedirectIfAuthenticated.phpファイルの設定を行います。$guardの値がadminの場合はRouteServiceProviderのADMIN_HOMEにリダイレクトする設定を行っています。


public function handle(Request $request, Closure $next, ...$guards)
{
    
    $guards = empty($guards) ? [null] : $guards;

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

    return $next($request);
}

設定後にログインした状態で/admin/registerにアクセスすろ/admin/dashboardにリダイレクトされることが確認できます。

routesフォルダのadmin.phpファイルで/regisrer, /loginなどのルーティングにmiddlewareのguestが利用されているためにmiddewareのguestに対応するRedirectIfAuthenticated.phpファイルの設定を行っています。
fukidashi

ログイン前のリダイレクト

ログインする前に/admin/dashboardにアクセスを行うと/loginにリダイレクトされます。アクセス制限のある/admin/以下のページにアクセスした場合はadmin認証のログイン画面である/admin/loginにリダイレクトできるように設定を行います。web.phpファイルで追加したルーティングへアクセスの制限はmiddlewareのauthによって制御されているのでAuthenticate.phpファイルを更新します。認証が行われていない場合はredirectToメソッドが実行されます。デフォルトでは/loginにリダイレクトされるように設定されているのでadmin以下のURLからアクセスの場合はadmin.login(=/admin/login)にリダイレクトするように変更を行います。リクエストの変数$requestにはアクセスしたURLの情報を持っているのでisメソッドを利用することでadmin/*と一致するパスの場合はtrueが戻されます。*はワイルドカードとして利用できます。


protected function redirectTo($request)
{

    if (! $request->expectsJson()) {
        if($request->is('admin/*')) return route('admin.login');
        return route('login');
    }
}

Authenticate.phpを更新してログイン前に/admin/dashboardにアクセスすると/admin/loginにリダイレクトされるようになります。

ログアウト処理

ログインしたユーザをログアウトする場合はAuthenticatedSessionController.phpファイルのdestoryメソッドが実行されます。

デフォルトではguardのwebが設定されているのでadminに変更します。リダイレクト先が”/”に設定さているので/admin/loginに変更を行っています。リダイレクト先は任意なので好きな場所を指定してください。


public function destroy(Request $request)
{
    Auth::guard('admin')->logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/admin/login');
}

ログイン処理

ユーザの登録までの設定が完了できたので/admin/login画面からログインを行います。登録済みユーザでログインしようとしてもログインを行うことができません。

ログインの失敗
ログインの失敗

ログイン処理を行うHttp¥Controllers¥Admin¥Auth¥AuthenticatedSessionController.phpファイルのstoreメソッドを確認します。authenticateメソッド中で認証のチェックが行われているのでstoreの引数に設定されているLoginRequeset.phpファイルの中身を確認します。ログイン後のリダイレクト先の場所をHOMEからADMIN_HOMEに変更しています。


public function store(LoginRequest $request)
{
    $request->authenticate();

    $request->session()->regenerate();

    return redirect()->intended(RouteServiceProvider::ADMIN_HOME);
}

LoginRequest.phpファイルの中ではauthenticateで認証のチェック(Auth::attempt)を行っていますがデフォルトではguardを指定しないためデフォルトのwebが利用されます。webを利用した場合はusersテーブルでemailとパスワードをチェックするためadminsテーブルに登録したユーザでは認証に失敗します。


public function authenticate()
{
    $this->ensureIsNotRateLimited();

    if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
        RateLimiter::hit($this->throttleKey());

        throw ValidationException::withMessages([
            'email' => __('auth.failed'),
        ]);
    }

    RateLimiter::clear($this->throttleKey());
}

リクエストの変数$requestにはアクセスしてきたURLの情報を持っているのでここでもその情報を利用します。URLがadmin/*の場合は$guardの値をadminにし、それ以外の場合はwebにしています。


public function authenticate()
{
    $this->ensureIsNotRateLimited();

    $this->is('admin/*') ? $guard = 'admin' : $guard = 'web';

    if (! Auth::guard($guard)->attempt($this->only('email', 'password'), $this->boolean('remember'))) {
        RateLimiter::hit($this->throttleKey());

        throw ValidationException::withMessages([
            'email' => __('auth.failed'),
        ]);
    }

    RateLimiter::clear($this->throttleKey());
}

設定後、/admin/loginからユーザ登録したユーザでログインを行うとadmin用のダッシュボードが表示されます。ここまでの設定で追加したadmin認証でもユーザの登録、ユーザのログイン、ログアウト処理がデフォルトの認証とは独立して動作するようになりました。

パスワードリセットの設定

パスワードリセットについてはどのような流れになっているか理解する必要があるためコードの中をこれまでよりも詳しく見ています。Laravelのビギナーの人にとってはファサードやサービスプロバイダーという言葉が出てくるので少し難しい内容になっています。

追加したadmin認証を利用してユーザの登録、ログイン処理が行えるようになりました。デフォルトの認証のようにパスワード機能をadmin認証でも利用したい場合は追加の設定が必要になります。

パスワードのリセットを行いたい場合はログイン画面にあるForgot your passwordをクリックします。

Forgot your passwordのリンク
Forgot your passwordのリンク

パスワードを忘れたメールアドレスを入力する画面が表示されます。手順通りに設定を行っている場合はURLは/admin/forgot-passwordとなります。

メールアドレス入力画面
メールアドレス入力画面

admin認証で登録したメールアドレスを入力してもメールアドレスからユーザを見つけられないとエラーメッセージが表示されます。

ユーザが存在しないエラー
ユーザが存在しないエラー

“EMAIL PASSWORD RESET LINK”ボタンをクリックすると/admin/forgot-passwordにPOSTリクエストが送信されるのでroutes/admin.phpファイルを確認します。実行されるのはPasswordResetLinkController.phpファイルのstoreメソッドであることがわかります。


Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
                ->middleware('guest:admin')
                ->name('password.email');

バリデーションでemailアドレスが入力されるかをチェックしPasswordファサードからsendResetLinkを実行しています。sendResetLinkメソッドの中でメールアドレスのチェックを行いリセットメールを送信、その後メッセージを元のページに戻すという流れになっています。


public function store(Request $request)
{
    $request->validate([
        'email' => ['required', 'email'],
    ]);


    // We will send the password reset link to this user. Once we have attempted
    // to send the link, we will examine the response then see the message we
    // need to show to the user. Finally, we'll send out a proper response.
    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status == Password::RESET_LINK_SENT
                ? back()->with('status', __($status))
                : back()->withInput($request->only('email'))
                        ->withErrors(['email' => __($status)]);
}

Passwordファサード

sendResetLinkメソッドがどのような処理を行っているか確認するためにsendResetLinkメソッドがどのファイルに存在するのか調べます。まずIlluminate¥Support¥Facades¥Password.phpの内容を確認します。getFacadeAccessorでauth.passwordを戻しているだけなのでサービスプロバイバーを確認する必要があります。


class Password extends Facade
{

//略

    protected static function getFacadeAccessor()
    {
        return 'auth.password';
    }
}

config/app.phpファイルを見るとパスワードリセットに関連するPasswordResetServiceProvider.phpという名前のサービスプロバーダーがproviderの配列の中に含まれているのでこのファイルを確認します。

PasswordResetServiceProvider.phpファイルを見るとPassword.phpファイルのgetFacadeAccessorメソッドの戻り値であるauth.passwordを見つけることができます。


class PasswordResetServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerPasswordBroker();
    }

    /**
     * Register the password broker instance.
     *
     * @return void
     */
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            return new PasswordBrokerManager($app);
        });

        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }

    //略
}

Passwordファサードの実体はPasswordBrokerManagerインスタンスであることがわかります。sendResetLinkメソッドはPasswordBrokderManager.phpファイルの中には存在しませんが、PasswordBrokderManager.phpファイル内のcallメソッド、borkerメソッド、resolveメソッドと順番に実行され最終的にresetメソッドでPasswordBrokerインスタンスが戻されるようになっています。


protected function resolve($name)
{
    $config = $this->getConfig($name);

    if (is_null($config)) {
        throw new InvalidArgumentException("Password resetter [{$name}] is not defined.");
    }

    // The password broker uses a token repository to validate tokens and send user
    // password e-mails, as well as validating that password reset process as an
    // aggregate service of sorts providing a convenient interface for resets.
    return new PasswordBroker(
        $this->createTokenRepository($config),
        $this->app['auth']->createUserProvider($config['provider'] ?? null)
    );
}

PasswordBroker.phpファイルの中にsendResetLinkメソッドが存在します。


public function sendResetLink(array $credentials, Closure $callback = null)
{
    // First we will check to see if we found a user at the given credentials and
    // if we did not we will redirect back to this current URI with a piece of
    // "flash" data in the session to indicate to the developers the errors.
    $user = $this->getUser($credentials);

    if (is_null($user)) {
        return static::INVALID_USER;
    }

    if ($this->tokens->recentlyCreatedToken($user)) {
        return static::RESET_THROTTLED;
    }

    $token = $this->tokens->create($user);

    if ($callback) {
        $callback($user, $token);
    } else {
        // Once we have the reset token, we are ready to send the message out to this
        // user with a link to reset their password. We will then redirect back to
        // the current URI having nothing set in the session to indicate errors.
        $user->sendPasswordResetNotification($token);
    }

    return static::RESET_LINK_SENT;
}

config/auth.phpファイルの確認

本文書の中でもadmin認証の追加設定のためにconfig/auth.phpに新たにadmin Guardを追加しました。追加した際にパスワードに関する設定を行ったことを思い出してください。


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

guardの時も何も指定していない時はweb guardが利用されていた通り、パスワードリセットに関しても何も設定していない場合はデフォルトのusersが利用されることになります。それはPasswordBrokerManager.phpファイルのbrokerメソッドから確認することができます。


public function broker($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->brokers[$name] ?? ($this->brokers[$name] = $this->resolve($name));
}

マジックメソッドの__callメソッドでbrokerが実行されていますが引数に何も設定されていません。


public function __call($method, $parameters)
{
    return $this->broker()->{$method}(...$parameters);
}}

brokerメソッドに引数がないので$this->getDefaultDriverメソッドにより”users”が戻されます。つまりデフォルトのままではメールアドレスの確認はusersテーブルをチェックすることになり、先ほどadmin認証で登録済みのメールアドレスを入力してもメールが存在しないことになります。

メールのチェックをadminsで実行するためにbrokerメソッドにadminを設定してみましょう。

PasswordResetLinkController.phpファイルのPasswordファサードの後にbrokerメソッドを追加します。


$status = Password::broker('admins')->sendResetLink(
    $request->only('email')
);

設定後に再度リセットメールが送信されるか確認してみましょう。メッセージに”We have emailed your password reset link”と表示されるように正しく送信することができるようになりました。

リセットメールの送信
リセットメールの送信

送信したメールアドレスに表示されているRest Passwordボタンをクリックします。

リセットメールの受信
リセットメールの受信

新しいパスワードの入力を行っても”We can’t find a user with that email address”というメッセージが表示されパスワードの変更を行うことができません。

メールアドレスが存在しない
メールアドレスが存在しない

リセットメールの送信内容

パスワードリセットメールを送信することができたので次は送信されるメールの中身がどのようなものになっているか確認していきます。

PasswordBroker.phpファイルのsendResetLinkメソッドの中にsendPasswordResetNotificationメソッドがあります。メソッドの名前はパスワードリセットの通知を送信すると意味を持っているのでこのメソッドの中でメール送信の処理が記述されているのではないかという予想が立ちます。


$user->sendPasswordResetNotification($token);

sendPasswordResetNotificationメソッドが記述されている場所を見つけるためにUser.phpファイルを確認します。User.phpファイルの中には直接sendPasswordResetNotificationメソッドの記述はないので継承している認証に関係するAuthenticableを確認します。AuthenticableはIlluminate\Foundation\Auth\UserのエイリアスなのでUser.phpを確認します。


class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

User.phpの中にパスワードリセットに関するtraitのCanResetPasswodを見つけることができます。そのCanResetPasswod.phpファイルを見るとsendPasswordResetNotificationメソッドを見つけることができnofity関数の引数にResetPasswordNotificationクラスを指定しています。


namespace Illuminate\Auth\Passwords;

use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;

trait CanResetPassword
{

//略

    /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new ResetPasswordNotification($token));
    }
}

ResetPasswordNotificationはResetPasswordのエイリアスなのでResetPassword.phpファイルを確認します。

Notification(通知)ではResetPassword.phpファイルのtoMailメソッドが実行されるのでtoMailメソッドの中を見ると変数$urlにrouter関数を使ってリンク先が設定されていることがわかります。これはデフォルト認証の/reset-passwodへのパスなので/admin/reset-passwordに変更を行う必要があります。しかしこのファイルを更新するとデフォルト認証のパスが変わるため変更を行うことできません。


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('password.reset', [
            'token' => $this->token,
            'email' => $notifiable->getEmailForPasswordReset(),
        ], false));
    }

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

送信するメールの内容はbuildMailMessageメソッドの中に記述されています。$urlを変更するとメール内のリンクも変わることがわかります。


protected function buildMailMessage($url)
{
    return (new MailMessage)
        ->subject(Lang::get('Reset 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.'));
}

ResetPassword.phpファイルを更新するのではなくResetPassword.phpファイルをコピーして新たにAdminResetPassword.phpファイルを作成します。

作成後toMailメソッドの$urlの設定を変更します。admin.を追加しています。


$url = url(route('admin.password.reset', [
    'token' => $this->token,
    'email' => $notifiable->getEmailForPasswordReset(),
], false));

buildMailMessageメソッドではデフォルトの設定を利用してリセットの有効時間を設定しているのでadminsの設定から取得できるように変更します。config関数はヘルパー関数でconfig/app.phpから指定したパラメータの値を取得することができます。リセットメールを送信する際にデフォルトのメールと区別できるようにsubjectにAdminを追加しています。


protected function buildMailMessage($url)
{
    return (new MailMessage)
        ->subject(Lang::get('Admin Reset 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.admins.expire')]))
        ->line(Lang::get('If you did not request a password reset, no further action is required.'));
}

app¥Moldes¥Admin.phpファイルにsendPasswordResetNotificationメソッドを追加します。


namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Auth\Notifications\AdminResetPassword as ResetPasswordNotification;

class Admin extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    public function sendPasswordResetNotification($token){

        $this->notify(new ResetPasswordNotification($token));
    }

//略

パスワードリセットを再度実行するとリンクのURLが/reset/password/…から/admin/reset-passwordに変わったメールが届きます。メールの下部にリンクの文字列が表示されているのでそこを確認してください。http://127.0.0.1:8000/admin/reset-password…となっています。

リンクの正しいリセットメール到着
リンクの正しいリセットメール到着

“Reset Password”ボタンクリック後の/admin/reset-passwordのアクセス時にadmin認証側のNewPasswordController.phpファイルが正しく設定されているか確認します。createメソッドではadmin.auth.reset-passwordにしておく必要があります。


public function create(Request $request)
{
    return view('admin.auth.reset-password', ['request' => $request]);
}

resources¥views¥admin¥authのreset-password.blade.phpファイルを開きます。POSTリクエストの送信先がadmin.password.updateになっていることを確認します。


<form method="POST" action="{{ route('admin.password.update') }}">

送信先のadmin.password.updateのNewPasswordControllerのstoreメソッドを確認します。パスワードのチェックを行う際はadminsテーブルを利用するのでPasswordファサードの後にbroker(‘admins’)を追加しています。リダイレクトされる場所がloginになっているのでadmin.loginへの更新を行います。


public function store(Request $request)
{
    $request->validate([
        'token' => ['required'],
        'email' => ['required', 'email'],
        'password' => ['required', 'confirmed', Rules\Password::defaults()],
    ]);

    // Here we will attempt to reset the user's password. If it is successful we
    // will update the password on an actual user model and persist it to the
    // database. Otherwise we will parse the error and return the response.
    $status = Password::broker('admins')->reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function ($user) use ($request) {
            $user->forceFill([
                'password' => Hash::make($request->password),
                'remember_token' => Str::random(60),
            ])->save();

            event(new PasswordReset($user));
        }
    );

    // If the password was successfully reset, we will redirect the user back to
    // the application's home authenticated view. If there is an error we can
    // redirect them back to where they came from with their error message.
    return $status == Password::PASSWORD_RESET
                ? redirect()->route('admin.login')->with('status', __($status))
                : back()->withInput($request->only('email'))
                        ->withErrors(['email' => __($status)]);
}

ここまでの設定が完了したらリセットメールに表示されているボタンをクリックしてパスワードの更新を行ってください。パスワードの更新ができ、更新後に再度新しいパスワードでログインできるか確認を行ってください。

まとめ

本文書ではLaravel 8の認証パッケージであるLaravel Breezeでマルチ認証を設定する方法について説明を行いました。またユーザ登録とログイン、ログアウトに加えパスワードリセットの設定方法についても動作確認します。BreezeにはVerifyEmailという機能がありますがその機能については動作確認を行なっていません。

ただ変更を行うだけではなく変更前と後の動作についても説明を行なっているのでJetStreamを利用した場合でも活用できるはずです。機会があれば本文書を参考にLaravel8でマルチ認証にぜひチャレンジしてみてください。