Laravel11で認証機能(Breeze)をカスタマイズする

こんにちは、コバヤシです。 以前Laravel6で認証をカスタマイズする方法を書きましたが、

tech.arms-soft.co.jp

今回はLaravel11でBreezeを使用した認証をカスタマイズする方法を書いていきます。

Breezeがインストールされていて動作していることを前提としています。

目次

参照する項目を追加するだけの場合

app/Http/Requests/Auth/LoginRequest.php
のauthenticateメソッドを変更します。

<?php
    public function authenticate(): void
    {
        $this->ensureIsNotRateLimited();

        $this->merge(['status' => 1]); // ステータスを追加

        if (! Auth::attempt($this->only('email', 'password', 'status'), $this->boolean('remember'))) { // ステータスも渡すようにする
            RateLimiter::hit($this->throttleKey());

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

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

これだけでOKです。

もう少し複雑な認証をしたい場合

ガードの作成

laravel/app/Guards/MyGuard.php

<?php

namespace Illuminate\Auth;

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
}

プロバイダーの作成

laravel/app/Providers/MyAuthServiceProvider.phpを作成します。

<?php

namespace App\Providers;

use Closure;
use App\Models\User;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\ServiceProvider;

class MyAuthServiceProvider extends EloquentUserProvider
{


    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->newModelQuery($model)
                    ->where($model->getAuthIdentifierName(), $identifier)
                    ->where('status', 1) // 必要な条件を追加
                    ->first();
    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed  $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, #[\SensitiveParameter] $token)
    {
        $model = $this->createModel();

        $retrievedModel = $this->newModelQuery($model)->where(
            $model->getAuthIdentifierName(), $identifier
        )
        ->where('status', 1) // 必要な条件を追加
        ->first();

        if (! $retrievedModel) {
            return;
        }

        $rememberToken = $retrievedModel->getRememberToken();

        return $rememberToken && hash_equals($rememberToken, $token) ? $retrievedModel : null;
    }

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(#[\SensitiveParameter] array $credentials)
    {
        $credentials = array_filter(
            $credentials,
            fn ($key) => ! str_contains($key, 'password'),
            ARRAY_FILTER_USE_KEY
        );

        if (empty($credentials)) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery()
                                 ->where('status', 1) // 必要な条件を追加

        foreach ($credentials as $key => $value) {
            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } elseif ($value instanceof Closure) {
                $value($query);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }
}

laravel/app/Providers/AuthServiceProvider.phpを作成します。

<?php

namespace App\Providers;

// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The model to policy mappings for the application.
     *
     * @var array<class-string, class-string>
     */
    protected $policies = [
        //
    ];

    /**
     * Register any authentication / authorization services.
     */
    public function boot(): void
    {
        $this->registerPolicies();

        Auth::extend('my_auth', function ($app, $name, array $config) {
            // 新しいガードを作成
            $guard = new \App\Guards\MyGuard($name, Auth::createUserProvider($config['provider']), $app['session.store']);


            if (method_exists($app, 'make')) {
                $guard->setDispatcher($app->make('events'));
            }

            if (method_exists($guard, 'setCookieJar')) {
                $guard->setCookieJar($app['cookie']);
            }

            // リクエストを設定
            $guard->setRequest($app->refresh('request', $guard, 'setRequest'));

            return $guard;
        });

        Auth::provider('my_auth', function ($app, array $config) {
            return new MyAuthServiceProvider($app['hash'], $config['model']);
        });
    }
}

追加したプロバイダーを使用するように設定します。
laravel/bootstrap/providers.php

<?php

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class, // 追加
];

configの変更

laravel/config/auth.phpを変更します。

<?php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'my_auth' => [ // 追加
            'driver' => 'taisyoku',
            'provider' => 'taisyoku',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
        'my_auth' => [ // 追加
            'driver' => 'taisyoku',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
    ],
  
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
        'my_auth' => [ // 追加
            'driver' => 'taisyoku',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],
    ],

コントローラーの修正

laravel/app/Http/Controllers/Auth/AuthenticatedSessionController.phpを変更します。

<?php
    /**
     * Destroy an authenticated session.
     */
    public function destroy(Request $request): RedirectResponse
    {
        Auth::guard('my_auth')->logout(); // 変更

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

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

        return redirect('/');
    }

envに追加

envに以下を追加します。

AUTH_GUARD=my_auth

完了

あとは、routesで

<?php
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');
});

のように使用すればOKです。

遷移先の設定

追加でログイン関係の遷移先を設定します。
以前はミドルウェアで設定していましたが、Laravel11からは設定方法が変わりました。
laravel/bootstrap/app.phpのwithMiddlewareに追記します。

<?php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        commands: __DIR__ . '/../routes/console.php',
        health: '/jXE7YmqkThQQ',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->redirectGuestsTo('/login'); // ログインしていない場合の遷移先
        $middleware->redirectUsersTo('/dashboard'); // ログインしていた場合の遷移先
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

まとめ

Laravel11からファイルがデフォルトでは無くなっていたり、設定する箇所が変わったりしましたが認証のカスタマイズ方法はそれほど変わっていませんでした。 今回はパスワード変更まで出来なかったので次回そこを書きたいと思います。