Laravel Breezeでマルチ認証(複数ユーザーの認証)に対応する方法です。認証システムをLaravel UIからBreezeに移行するための備忘録です。
Laravel UIでは認証系コントローラーでTraitを実装していたのでguard
メソッドを上書きすることでガードの設定ができましたが、Laravel Breezeでは各コントローラーで処理が完結しているので1つずつ設定する必要があります。
なお、Breezeのインストールまで完了しているところからの解説となります。
環境
- PHP 8.1
- Laravel 10.15
- Laravel Breeze 1.21
認証用モデルを追加
CustomUser
モデルとパスワードリセット用のテーブルを作ります。内容はデフォルトのUser
モデルを一旦コピペします。
なお、モデルとガードの名前はお好みで変更してください。
php artisan make:model CustomUser -m
php artisan make:migration create_custom_user_password_reset_tokens_table
<?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 Laravel\Sanctum\HasApiTokens;
class CustomUser extends Authenticatable
{
// 以下は app/Models/User.php の内容をそのままコピペ
...
public function up(): void
{
Schema::create('custom_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();
});
}
...
...
public function up(): void
{
Schema::create('custom_user_password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
...
認証ガードを登録
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'custom_user' => [
'driver' => 'session',
'provider' => 'custom_users',
],
],
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'custom_users' => [
'driver' => 'eloquent',
'model' => App\Models\CustomUser::class,
],
],
...
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
'custom_users' => [
'provider' => 'custom_users',
'table' => 'custom_user_password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
...
認証系コントローラーにガードを適用する
続いて認証系のコントローラーにガードを適用してきます。
まずは、app/Http/Controllers/CustomUser/Auth
というディレクトリを作成し、 app/Http/Controllers/Auth
にあるファイルを全てコピペします。
次に、コピペしたファイルのnamespaceを App\Http\Controllers\
に変更します。CustomUser
\Auth
ようやくコントローラーを修正していきます。コードをすべて記載すると記事が長くなってしまうので変更点に絞って記載します。少し面倒ですが1つずつ変更していきましょう。
public function destroy(Request $request): RedirectResponse
{
Auth::guard('custom_user')->logout();
$request->session()->invalidate();
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('custom_user')->validate([
'email' => $request->user('custom_user')->email,
'password' => $request->password,
public function store(Request $request): RedirectResponse
{
if ($request->user('custom_user')->hasVerifiedEmail()) {
return redirect()->intended('/custom'.RouteServiceProvider::HOME);
}
$request->user('custom_user')->sendEmailVerificationNotification();
public function __invoke(Request $request): RedirectResponse|Response
{
return $request->user('custom_user')->hasVerifiedEmail()
? redirect()->intended('/custom'.RouteServiceProvider::HOME)
public function store(Request $request): RedirectResponse
{
...
$status = Password::broker('custom_users')->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));
}
);
public function update(Request $request): RedirectResponse
{
...
$request->user('custom_user')->update([
'password' => Hash::make($validated['password']),
]);
public function store(Request $request): RedirectResponse
{
...
$status = Password::broker('custom_users')->sendResetLink(
$request->only('email')
);
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:'.CustomUser::class,
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = CustomUser::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::guard('custom_user')->login($user);
return redirect('/custom'.RouteServiceProvider::HOME);
}
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user('custom_user')->hasVerifiedEmail()) {
return redirect()->intended('/custom'.RouteServiceProvider::HOME.'?verified=1');
}
if ($request->user('custom_user')->markEmailAsVerified()) {
event(new Verified($request->user('custom_user')));
}
return redirect()->intended('/custom'.RouteServiceProvider::HOME.'?verified=1');
}
LoginRequestを修正
Laravel Breezeでは実際の認証処理は App\Http\Requests\Auth\LoginRequest
で行っています。CustomUser用のリクエストを作成してもいいのですが、LoginRequest
を少し修正するだけで流用できます。
public function authenticate(string $guard = 'web'): void
{
$this->ensureIsNotRateLimited();
if (! Auth::guard($guard)->attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate('custom_user');
$request->session()->regenerate();
テストファイルとミドルウェアを修正
最後にテストファイルとミドルウェアを修正しましょう。
テストでは actingAs
メソッドの第二引数にガードを渡して認証モデルを指定できます。
$response = $this->actingAs($user, 'custom_user')->get('/');
認証に関するミドルウェアは
App\Http\Middleware\Authenticate
: 認証前のユーザーをログイン画面にリダイレクトApp\Http\Middleware\RedirectIfAuthenticated
: 認証済みのユーザーを管理画面などにリダイレクト
の2つです。
protected function redirectTo(Request $request): ?string
{
if ($request->expectsJson()) {
return null;
}
if ($request->routeIs('custom.*')) {
return route('custom.login');
}
return route('login');
}
public function handle(Request $request, Closure $next, string ...$guards): Response
{
if (Auth::guard('custom_user')->check()) {
return redirect('/custom'.RouteServiceProvider::HOME);
}
guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
カスタムガードのリダイレクト先も既存の実装にならって RouteServiceProvider
の定数で指定してもいいかもしれません。
以上です。
コメント