Webアプリケーションでは、ユーザー用の管理画面(いわゆるマイページ)と、管理者用の管理画面が別々に存在することも多いです。その場合、ログイン画面やDBテーブルも別になることが一般的でしょうか。
このように、1つのアプリケーションで個別の認証を持つことを、マルチ認証(Multi Auth)と言います。
今回はそんなマルチ認証を仕事で実装することになったのですが、機能はシンプル&低予算ということで、Breezeで実装することにしました。
実はこれまでも何度もやっていますが、その場対応でやっているので、何となくの理解です。加えて多くはLaravel9~10時代でした。
そこで今回はLaravel11×Breezeによる、マルチ認証の方法を書き留めておきたいと思います。
やりたいこと
あらためて現段階でやることを言語化してみます。
- ユーザー(
users)と管理者(admins)として別々のテーブルを持つ - 別々のモデルクラス、
User.php,Admin.php - デフォルトでは
/loginなのを、それぞれ/user/login,/admin/loginとしたい
とてもシンプルです。
手順としては、最初からあるBreezeのログインが/loginなのでそれを/user/loginとするところまで先に完了させます。その後、複製する形で管理者版を作る感じです。
ここまでをひな形として作っておけば、使い回しできそうです。
早速進めましょう。
はじめに
Laravelインストール
Laravelのインストールが出来ている状態からはじめます。
まだな方は以下をご参考ください。
Breezeインストール
Breezeも、以下のコマンドでインストール&初期化済みとします。
composer require laravel/breeze --dev
php artisan breeze:install
npm installphp artisan migrateはまだしていません。
セッションをfileに
今回の案件は複数サーバー構成をとらないので、セッションはfileで管理します。
laravel11はデフォルトでdatabaseになっているので、.envにSESSION_DRIVER=fileを足します。
# ~略~
SESSION_DRIVER=file
# ~略~ついでにDBの初期設定やタイムゾーン、メール設定など各自環境にあわせてやっておきます。
セッション管理にDBは使わないので、database\migrations\0001_01_01_000000_create_users_table.phpのsessionsテーブルは作らないでおきます。必要になった時に作れば良いかなと(もちろん今作っても可)。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', 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();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
// コメントアウト
// Schema::create('sessions', function (Blueprint $table) {
// $table->string('id')->primary();
// $table->foreignId('user_id')->nullable()->index();
// $table->string('ip_address', 45)->nullable();
// $table->text('user_agent')->nullable();
// $table->longText('payload');
// $table->integer('last_activity')->index();
// });
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
// コメントアウト
// Schema::dropIfExists('sessions');
}
};
ここでマイグレートします。
php artisan migrateuserディレクトリにファイルを移動
もともとのBreezeはマルチ認証ではないため、userに関する操作、例えばログインでは/loginというパスになっています。
今回マルチ認証にする上で、これをuser/loginなどとしてアクセスできるように、既存のユーザー認証を/user配下に全て移動することにします。
そのためにいろいろと設定やファイルを移動・整理します。
なぜ移動するのか?
ユーザーに関するBreezeのファイルはそのままで、新規で/adminだけ追加で作ってももちろん良いです。
ただ、個人的にはUser, Adminは並列の関係なので、揃えた方がスッキリ&分かりやすいように思いました。
この作業が不要な方は行う必要はありませんが、今回は行う前提で進めます。
ビューの移動
ユーザー用のディレクトリ、resources\views\にweb\userディレクトリを作ります。
作成したディレクトリの中に、以下のBreeze関連のディレクトリ、ファイルを放り込みます(=移動)。
resources\views\dashboard.php-
resources\views\auth resources\views\profile
加えてレイアウト関連も移動しましょう。
resources\views\layoutsにuserディレクトリを作ります。
作成したディレクトリの中に、以下のファイルを放り込みます。
- app.blade.php
- guest.blade.php
- navigation.blade.php

あまり無さそうですが、レイアウトファイルをユーザーと管理者で共有する場合は、やらなくてもOKです。
フォームリクエストの移動
ユーザー用のディレクトリ、app\Http\Requests\にUserディレクトリを作ります。
作成したディレクトリの中に、以下のファイルを放り込みます。
app\Http\Requests\ProfileUpdateRequest.phpapp\Http\Requests\Auth
こんな感じのディレクトリ構成になります。

ディレクトリ構成が変わるため、移動したファイルのnamespaceのパスを修正しておきます。
私は、App\Http\Requests→App\Http\Requests\Userに置き換えました。※今回は2個だけの置換。
コントローラーの移動
まず、app\Http\Controllers\にUserディレクトリを作ります。
そして作成したそのディレクトリに、既存の以下を放り込みます。
app\Http\Controllers\ProfileController.phpapp\Http\Controllers\Auth
これでBreeze関連のファイルが、Userディレクトリに完全に収まりました。
忘れずに、今回も移動したファイルのnamespace一式を変えておきます。
app\Http\Controllers\User\Auth\AuthenticatedSessionController.phpの例ですが、3行目を変えてます。
ついでに、先ほどフォームリクエストも移動しているので直しておきましょう(以下7行目)。
<?php
// 以下のようにUserが付くように修正
namespace App\Http\Controllers\User\Auth;
use App\Http\Controllers\Controller;
// ↓Userディレクトリに移動したためこれも修正
use App\Http\Requests\User\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthenticatedSessionController extends Controller
{フォームリクエストの変更はapp\Http\Controllers\User\ProfileController.phpにもあります。さらに以下3行目のようにControllerを新たにuseする必要がありますね。
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{ルート
auth.phpをweb_user.phpに変更し、web.phpも以下の様に整理しました。ここでコメントアウトした行は、この後web_user.phpに移動予定です。
<?php
// use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
// Route::get('/dashboard', function () {
// return view('dashboard');
// })->middleware(['auth', 'verified'])->name('dashboard');
// Route::middleware('auth')->group(function () {
// Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
// Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
// Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// });
Route::prefix('user')->name('user.')->group(function () {
require __DIR__ . '/web_user.php';
});
// ↑のweb_user.phpを読むことにする
// require __DIR__.'/auth.php';上記設定で、web_user.php内の定義は強制的に/userというプリフィクスがつくようになります。
そしてweb_user.phpでは、guestとauthミドルウェア等(認証部分)を定義しています。
<?php
// 追加(パスにUserを追加)
use App\Http\Controllers\User\Auth\AuthenticatedSessionController;
use App\Http\Controllers\User\Auth\ConfirmablePasswordController;
use App\Http\Controllers\User\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\User\Auth\EmailVerificationPromptController;
use App\Http\Controllers\User\Auth\NewPasswordController;
use App\Http\Controllers\User\Auth\PasswordController;
use App\Http\Controllers\User\Auth\PasswordResetLinkController;
use App\Http\Controllers\User\Auth\RegisteredUserController;
use App\Http\Controllers\User\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route;
// 追加(パスにUserを追加)
use App\Http\Controllers\User\ProfileController;
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])
->name('password.store');
});
Route::middleware('auth')->group(function () {
// プロフィール関連をweb.phpから移動
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('verify-email', EmailVerificationPromptController::class)
->name('verification.notice');
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
->name('password.confirm');
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
->name('logout');
// dashboard関連をweb.phpから移動&修正
Route::middleware('verified')->group(function () {
Route::get('/dashboard', function () {
return view('web.user.dashboard');
})->name('dashboard');
// ここからverifiedなルートを追加可能
});
});
ファイルの分け方は案件や好みによります。今回の方法はユーザーのルートが分けられるメリットがあります。
反面、web_user.phpが肥大するので、先ほど触れたとおり、必要に応じて認証部分のファイルを分けたりするとよいかもしれません。
各種パスの変更
さて、ここまででユーザー関連のディレクトリに、ファイルを移動してきました。切りの良いタイミングですので、このあたりで動作確認をしてみます。
設定やルートをいじったので、キャッシュをクリアしておいた方が良いかもです。
php artisan config:clear
php artisan route:clear各種パスを合わせる必要がある
人によってURLは変わると思いますが、例えばhttp://localhost/user/loginのようなログイン画面を表示してみます。
結論から言えば、以下の様に100%、Internal Server Errorとなります。

なぜなら、ファイルを移動しただけですので、コントローラ・ビュー(blade)内の各種パスがユーザー用に合っていないからです。
これらを改善していく必要があります。ここまでくれば、1つずつエラー内容を見ながら修正していけばk解決できるはずです。
……これがBreezeマルチ認証の最も面倒くさい部分なのですよね。
以下、私が行った内容をメモしておきます。
レイアウトファイルの修正
先ほどのエラーは、layout.guestというビューが無いと言っています。
該当指定は、app\View\Componentsの中にある、以下の2ファイルにより指定されています。
- AppLayout.php
- GuestLayout.php
上記ファイルを一括置換で以下に変更すれば、エラーが出なくなります。
return view('layouts.→return view('layouts.user.
Bladeのルート・パス指定を修正
この状態でも、まだまだエラーが出ます。loginというルートは無いですよということです。

これは、ルート名にプリフィクスをつけたため、user.login等としないといけないからです。
resource/viewsの中にはこのような指定が数多くあります。これらも一式変更しなければなりません。
これまでと同じように、resource/views内を対象にした検索置き換えで、以下のように一式置き換えます。
layouts.→layouts.user.route('→route('user.
↑注:reset-password.blade.phpの$request->route('token')だけは置き変えないrouteIs('→routeIs('user.Route::has('→Route::has('user.@include('profile→@include('web.user.profile
……などなど、置き換えます。これでとりあえずはログイン画面が表示されるようになります。

置き換え多すぎ……。
コントローラーで指定するビュー・ルートの変更
View [auth.login] not found.というエラーが出ているかもしれません。もしくはログイン画面は表示されているけれど、ダッシュボードを開こうとするとまだ同じエラーがでているかもしれません。
ここでは、app\Http\Controllers\User\ディレクトリ内から読み込むビューファイルに、userプリフィクスを付けます。ここでは2回、一括置換で済みます。
該当ディレクトリ内をview('で検索し、view('web.user.に置き換えます。以下の様に、今回は7ファイル存在しました。

※初稿ではこちらが最初の置き換えだったのでスクショがありますが気にしないでください。
次は、コントローラー内で指定するルート指定を変更することでエラーを解消します。
具体的には、routeメソッドでのルートの指定を、先ほどと同一のディレクトリを対象に置き換えます。
route('→route('user.

パスワードリセット
これで一見できたかのように見えますが、まだまだやることはたくさんあります。
パスワードをリセットするページhttp://localhost/user/forgot-passwordからリセットをしようとするとRoute [password.reset] not defined.エラーが生じています。
パスワードリセットの処理は、以下のファイルに書かれています。
vendor\laravel\framework\src\Illuminate\Auth\Notifications\ResetPassword.php
protected function resetUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable, $this->token);
}
return url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}フレームワーク内でpassword.resetが指定されてしまうので、問題が生じているわけですね。
このファイルを修正するのはもちろんよろしくありません。ですのでこれを継承したクラスを作成&利用します。
以下のコマンドを打ち込みます。
php artisan make:notification CustomResetPasswordファイルが出来るので、以下の様に修正します。
<?php
namespace App\Notifications;
// use Illuminate\Bus\Queueable;
// use Illuminate\Contracts\Queue\ShouldQueue;
// use Illuminate\Notifications\Messages\MailMessage;
// use Illuminate\Notifications\Notification;
// 追加
use Illuminate\Auth\Notifications\ResetPassword as BaseResetPassword;
class CustomResetPassword extends BaseResetPassword
{
// use Queueable;
/**
* コンストラクタを置く場合は
* 親のコンストラクタを呼ばないとエラーになるので
* コメントアウトしておく
*/
// public function __construct()
// {
//
// }
/**
* Get the reset URL for the given notifiable (user or admin).
*
* @param mixed $notifiable
* @return string
*/
protected function resetUrl($notifiable)
{
// 管理者の場合は admin のルート、それ以外のユーザーの場合は user のルートを使用
if (request()->routeIs('admin.*')) {
return url(route('admin.password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
} else {
return url(route('user.password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
}
}
そしてapp/Models/User.phpモデルで、それを使用するようにします。
<?php
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 App\Notifications\CustomResetPassword;
class User extends Authenticatable
{
//(略)
// パスワードリセット通知のカスタマイズ
public function sendPasswordResetNotification($token)
{
$this->notify(new CustomResetPassword($token));
}
//(略)
}
これでパスワードのリセットができるようになりました。
ログインリダイレクト
表面化しづらいですが、まだ問題があります。まだログインされていない状態に、ダッシュボードhttp://localhost/user/dashboardへリクエストするとエラーになります。

このエラーで指定されているルート:loginは、以下で定義されています。
vendor\laravel\framework\src\Illuminate\Foundation\Configuration\ApplicationBuilder.php
<?php
namespace Illuminate\Foundation\Configuration;
use Closure;
use Illuminate\Console\Application as Artisan;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
use Illuminate\Contracts\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Bootstrap\RegisterProviders;
use Illuminate\Foundation\Events\DiagnosingHealth;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as AppEventServiceProvider;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as AppRouteServiceProvider;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Laravel\Folio\Folio;
class ApplicationBuilder
// (略)
public function withMiddleware(?callable $callback = null)
{
$this->app->afterResolving(HttpKernel::class, function ($kernel) use ($callback) {
$middleware = (new Middleware)
->redirectGuestsTo(fn () => route('login'));
if (! is_null($callback)) {
$callback($middleware);
}こちらもフレームワーク内のファイルを直接修正するわけにはいかないので、アプリケーション内で対策します。
これはミドルウェアで対処することになります。
ミドルウェアは、Laravel11からはbootstrap/app.phpを編集します。Adminはまだ作っていませんが、その処理もついでに書いておきましょう。
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
// 追加
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// リクエストルートが admin.* なら admin.login に、それ以外なら user.login にリダイレクト
$middleware->redirectGuestsTo(function (Request $request) {
return $request->routeIs('admin.*')
? route('admin.login')
: route('user.login');
});
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
これで、未認証時にログイン画面にリダイレクトされるようになりました。
ちなみにLaravel10までは、app\Http\Middleware\Authenticate.phpに書けば良いです。
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
if ($request->expectsJson()) {
return null;
}
if ($request->routeIs('admin.*')) {
return route('admin.login');
}
if ($request->routeIs('user.*')) {
return route('user.login');
}
return route('login');
}
}
メールアドレス認証
Webアプリケーションでは、メールアドレス認証をする、つまりUserモデルがMustVerifyEmailを実装する場合も多いですよね。
以下の様な感じです。
<?php
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 App\Notifications\CustomResetPassword;
class User extends Authenticatable implements MustVerifyEmail
{その場合、ユーザー登録時に以下の様にエラーになります。

こちらもフレームワーク内にルートverification.verifyの指定があります。
vendor\laravel\framework\src\Illuminate\Auth\Notifications\VerifyEmail.php
これも、自分で通知クラスを作りオーバーライドします。
以下のコマンドで通知クラスを作成します。
php artisan make:notification CustomVerifyEmailコマンドにより作られたapp\Notifications\CustomVerifyEmail.phpを、以下のように編集します。
<?php
namespace App\Notifications;
use Illuminate\Auth\Notifications\VerifyEmail as BaseVerifyEmail;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
class CustomVerifyEmail extends BaseVerifyEmail
{
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
// 管理者かユーザーかでルート名を切り替え
$routeName = request()->routeIs('admin.*')
? 'admin.verification.verify'
: 'user.verification.verify';
return URL::temporarySignedRoute(
$routeName, // ルート名を動的に設定
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
}
上記修正したファイルを、app/Models/User.phpで利用します。
<?php
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 App\Notifications\CustomResetPassword;
// 追加
use App\Notifications\CustomVerifyEmail;
class User extends Authenticatable implements MustVerifyEmail
{
//(略)
// メール認証通知のカスタマイズ
public function sendEmailVerificationNotification()
{
$this->notify(new CustomVerifyEmail());
}
//(略)
}
ミドルウェア
verification.verifyのエラーは解消しましたが、メールアドレス認証が済んでいない状態でダッシュボードにアクセスしようとすると、verification.noticeのエラーが発生します。

これは、vendor\laravel\framework\src\Illuminate\Auth\Middleware\EnsureEmailIsVerified.phpにて指定されています。以下のミドルウェアを作ります。
php artisan make:middleware CustomEnsureEmailIsVerifiedapp\Http\Middleware\CustomEnsureEmailIsVerified.phpが作られるので、以下の様にコードを修正します。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\URL;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified as BaseEnsureEmailIsVerified;
// 追加
use Illuminate\Contracts\Auth\MustVerifyEmail;
class CustomEnsureEmailIsVerified extends BaseEnsureEmailIsVerified{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null $guard
* @param string|null $redirectToRoute
*/
public function handle($request, Closure $next, $guard = null, $redirectToRoute = null)
{
$user = auth()->guard($guard)->user();
if (! $user || ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail())) {
$route = $redirectToRoute;
if ($guard === 'admin') {
$route = $redirectToRoute ?: 'admin.verification.notice';
} else {
$route = $redirectToRoute ?: 'user.verification.notice';
}
return $request->expectsJson()
? abort(403, 'Your email address is not verified.')
: Redirect::guest(URL::route($route));
}
return $next($request);
}
}
そしてこのミドルウェアをverifiedとして登録することで、デフォルトの設定を上書きします。
bootstrap/app.phpを以下の様に編集します。
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// リクエストルートが admin.* なら admin.login に、それ以外なら user.login にリダイレクト
$middleware->redirectGuestsTo(function (Request $request) {
return $request->routeIs('admin.*')
? route('admin.login')
: route('user.login');
});
// エイリアスとして追加
$middleware->alias([
'verified' => \App\Http\Middleware\CustomEnsureEmailIsVerified::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
これでメールアドレス認証がただしく動作するようになりました。
ログイン時のリダイレクト
Laravelでは、ログイン済みの場合に特定の画面にリダイレクトするミドルウェアがあります。
例えばログイン後にhttp://localhost/user/loginなどにアクセスすると、通常ダッシュボードに移動するのですが、現在はトップ(http://localhost/)に移動するようです。
これもきちんと設定しておかなければ不親切なサイトになってしまいます。
これはguestとして\Illuminate\Auth\Middleware\RedirectIfAuthenticated.phpが割り当てられているためです。独自ミドルウェアを作り上書きしてあげます。
以下のコマンドでミドルウェアを作成します。
php artisan make:middleware CustomRedirectIfAuthenticatedapp\Http\Middleware\CustomRedirectIfAuthenticated.phpができますので、私は以下の様に、defaultRedirectUri()メソッドをオーバーライドして実現してみました。
<?php
namespace App\Http\Middleware;
// 追加
use Illuminate\Auth\Middleware\RedirectIfAuthenticated as BaseRedirectIfAuthenticated;
class CustomRedirectIfAuthenticated extends BaseRedirectIfAuthenticated
{
/**
* Get the default URI the user should be redirected to when they are authenticated.
*/
protected function defaultRedirectUri(): string
{
if (request()->routeIs('admin.*')) {
return route('admin.dashboard');
}
return route('user.dashboard');
}
}
bootstrap/app.phpから登録します。
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// リクエストルートが admin.* なら admin.login に、それ以外なら user.login にリダイレクト
$middleware->redirectGuestsTo(function (Request $request) {
return $request->routeIs('admin.*')
? route('admin.login')
: route('user.login');
});
$middleware->alias([
'verified' => \App\Http\Middleware\CustomEnsureEmailIsVerified::class,
// 追加
'guest' => \App\Http\Middleware\CustomRedirectIfAuthenticated::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
これで正しくリダイレクトするようになりました。

これでひとまずユーザーは完了です!
Adminを作る
ユーザー関連を作りつつ、ミドルウェア等での切り分け処理もついでに書いてきたので、あとは簡単です。
Adminモデルを作る
\App\Models\User.phpをコピーし、\App\Models\Admin.phpを作っておきます。もちろん、ファイル内のクラス名も忘れず変えてください。
adminガードを作る
config\auth.phpを編集して、adminガードを作ります。
// ~(略)~
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
// 追加
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
// ~(略)~
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 追加
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
],
// ~(略)~
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
// 追加
'admins' => [
'provider' => 'admins',
// password_reset_tokensと共用で動作するようチャレンジもできそうだけどこの方が楽
'table' => 'admin_password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
フォームリクエストを複製する
app\Http\Requests\Userディレクトリを複製します。

そして、コントローラーの時と同様にいろいろ置換します。
意図しない置換を防ぐために、大文字小文字を区別するチェックをいれてから実施するのが良いでしょう。
User→Adminuser()→user('admin')Auth::attempt→Auth::guard('admin')->attempt
Controllerを複製する
app\Http\Controllers\Userディレクトリをコピーし、Adminディレクトリを作ります。
名称やuseなどのパスも全てAdminに変更する必要があります。
以下が置き換え参考例です。
User→Adminguard('web')→guard('admin')$request->user()→$request->user('admin')route('user.→route('admin.view('web.user.→view('web.admin.Auth::logout()→Auth::guard('admin')->logout()Auth::login(→Auth::guard('admin')->login(
ここで重要なのですが、以下のファイルがあります。
app\Http\Controllers\Admin\Auth\RegisteredUserController.php
一括置き換えでUserをAdminに変更すると、このファイルだけ、ファイル名とクラス名が合わなくなります。このファイル名はUserのままでいいやという方は、手作業で戻してください。
もしくは、Adminにしたいという場合はファイル名をRegisteredAdminController.phpに変更してください。
私はファイル名を変えない(RegisteredUserController)で進めます。
ビューコンポーネントを複製
app\View\Componentsにあるファイルをそれぞれ以下の様な名称で複製します。
AppLayout.php→AdminAppLayout.phpGuestLayout.php→AdminGuestLayout.php
クラス名やviewメソッドのパラメータも管理者用に変えます。
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AdminAppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.admin.app');
}
}
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AdminGuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.admin.guest');
}
}
ビューレイアウトを複製
resources\views\layoutsにあるuserを複製し、adminを作ります。
同様にディレクトリ内を以下で置き換えます。
user.→admin.
ビューを複製する
resources\views\web\userを複製してadminディレクトリを作ります。

あとは、resources\views\web\adminディレクトリ内を以下で置き換えます。
user.→admin.x-app-layout→x-admin-app-layoutx-guest-layout→x-admin-guest-layout
マイグレーションファイルを複製する
database\migrations\0001_01_01_000000_create_users_table.phpを参考に、管理者用のマイグレーションファイルを作っておきます。
上記ファイルを複製し、0001_01_03_000000_create_admins_table.phpとします。
私はこんな風にしてみました。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
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();
});
Schema::create('admin_password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
// Schema::create('admin_sessions', function (Blueprint $table) {
// $table->string('id')->primary();
// $table->foreignId('user_id')->nullable()->index();
// $table->string('ip_address', 45)->nullable();
// $table->text('user_agent')->nullable();
// $table->longText('payload');
// $table->integer('last_activity')->index();
// });
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('admins');
Schema::dropIfExists('admin_password_reset_tokens');
// Schema::dropIfExists('admin_sessions');
}
};
終わったら、以下のコマンドでマイグレートしておきます。
php artisan migrateルートを作る
routes\web_user.phpを複製して、routes\web_admin.phpを作り、ユーザー用になっているのを、各種置き換えます。
User→Adminuser→adminguest→guest:adminauth→auth:adminverified→verified:admin
ここもRegisteredUserControllerをUserのままにした場合は置き換え注意です。
<?php
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\PasswordController;
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;
use App\Http\Controllers\Admin\ProfileController;
Route::middleware('guest:admin')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])
->name('password.store');
});
Route::middleware('auth:admin')->group(function () {
// プロフィール関連をweb.phpから移動
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('verify-email', EmailVerificationPromptController::class)
->name('verification.notice');
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
->name('password.confirm');
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
Route::put('password', [PasswordController::class, 'update'])->name('password.update');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
->name('logout');
// dashboard関連をweb.phpから移動&修正
Route::middleware('verified:admin')->group(function () {
Route::get('/dashboard', function () {
return view('web.admin.dashboard');
})->name('dashboard');
// ここからverifiedなルートを追加可能
});
});
いましがた作ったファイルを、routes\web.phpに追加します。
<?php
// use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
// Route::get('/dashboard', function () {
// return view('dashboard');
// })->middleware(['auth', 'verified'])->name('dashboard');
// Route::middleware('auth')->group(function () {
// Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
// Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
// Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// });
Route::prefix('user')->name('user.')->group(function () {
require __DIR__ . '/web_user.php';
});
Route::prefix('admin')->name('admin.')->group(function () {
require __DIR__ . '/web_admin.php';
});
その他問題等
パスワードのテーブルがusersが使われてしまう問題
app\Http\Controllers\Admin\Auth\PasswordResetLinkController.php内を以下のように置き換えます。
Password::sendResetLink→Password::broker('admins')->sendResetLink
同様に、app\Http\Controllers\Admin\Auth\NewPasswordController.php内も以下の様に置き換えます。
Password::reset→Password::broker('admins')->reset
まとめ
いやー、面倒臭い。記事にするのは大変でした。これを書くのに、4~5回ほど最初からやり直したくらいです。
Laravel側で、やりやすい方法を用意してくれていたらいいんですけれどね。どうやら推奨されていないっぽくて、期待薄です。
間違い等がある可能性や、後から問題が出る可能性もあると思いますので、適宜加筆・修正したいと思います。
今回は私の例でしたが、参考になるところがありましたら幸いです。

くれぐれも、置き換えは慎重に。ディレクトリの対象範囲や、文字を誤って置き換えると、ごちゃごちゃになって、大変です(経験談)。




コメント