概要
EloquentUserProviderを継承して、独自のGuardを定義をします。
定義することによって、例えば、以下のよくあるユースケースの例のような、認証の微妙なカスタマイズが可能となります。
- 非会員であるユーザは、ログイン不可
- 非アクティブなステータスのユーザは、ログイン不可
- etc...
具体的には
やることに関しては、公式の「認証」>「カスタムユーザープロバイダの追加」のあたりが該当します。
config/auth.php
で定義しているdriver
を、'eloquent'
(EloquentUserProvider
)ではなく、拡張したものを使用するのがゴールです。
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
前準備
バージョン
# sail artisan --version
Laravel Framework 9.7.0
laravel/uiを用いた、auth関連の導入
composer require laravel/ui
php artisan ui bootstrap --auth
(Laravel7以降で導入可能
Modelを用意
- 認証に使用するModelに、
Illuminate\Foundation\Auth\User
を継承させておく。
<?php
namespace App\Models;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class AdminUser extends Authenticatable
{
// ...
実装
独自のAuthProviderの作成
AdminUserAuthProvider.php
を作成し、Illuminate\Auth\EloquentUserProvider
を継承します。
継承元のEloquentUserProvider
についてはこちら
(↓のディレクトリ位置は私の趣味です。ご自由に
<?php
namespace App\Providers\Auth;
use Illuminate\Auth\EloquentUserProvider;
class AdminUserAuthProvider extends EloquentUserProvider
{
/**
* {@inheritDoc}
*/
public function retrieveById($identifier)
{
$model = $this->createModel();
return $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
// AdminUser::scopeActive()
->active()
->first();
}
/**
* {@inheritDoc}
*/
public function retrieveByToken($identifier, $token)
{
$model = $this->createModel();
$retrievedModel = $this->newModelQuery($model)->where(
$model->getAuthIdentifierName(), $identifier
)
// AdminUser::scopeActive()
->active()
->first();
if (! $retrievedModel) {
return;
}
$rememberToken = $retrievedModel->getRememberToken();
return $rememberToken && hash_equals($rememberToken, $token)
? $retrievedModel : null;
}
/**
* {@inheritDoc}
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
str_contains($this->firstCredentialKey($credentials), 'password'))) {
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();
foreach ($credentials as $key => $value) {
if (str_contains($key, 'password')) {
continue;
}
if (is_array($value) || $value instanceof Arrayable) {
$query->whereIn($key, $value);
} elseif ($value instanceof Closure) {
$value($query);
} else {
$query->where($key, $value);
}
}
// AdminUser::scopeActive()
$query->active();
return $query->first();
}
}
オーバライドしている3つのメソッドは、それぞれ下記の認識です。
-
retrieveById()
- ログイン認証後の認証を行う。(ログイン後の認証保持
-
retrieveByToken()
-
remember_token
を用いた、ユーザ認証の取得 - おそらく、
remember_token
を使用しないなら不要
-
-
retrieveByCredentials()
- 実際のログイン認証時のパスワード認証
今回はModelのAdminUserにscopeActive()
を定義し、このスコープを用いて、アクティブなステータスのAdminUserを取得するようにしています。
実際に使用する場合は、各種ロジックにおいて、ユースケースに応じたロジックを追加するとよいです。
AuthServiceProvider
に、作成したAuthProviderを登録
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use App\Providers\Auth\AdminUserAuthProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
// 省略
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
// ↓を追加
// @see Illuminate\Auth\CreatesUserProviders::createUserProvider()
Auth::provider('admin_users', function ($app, array $config) {
return new AdminUserAuthProvider($this->app['hash'], $config['model']);
});
}
}
(Auth::provider()
が、あんまりドキュメントに残ってなくて鬼門でした...
config/auth.php
に登録
'providers' => [
// ↓ 登録したdriverで登録
'admin' => [
'driver' => 'admin_users',
'model' => AdminUser::class,
],
これでguardが作成できました。
あとは、このguardを用いたLoginController
や、認証まわりのルーティングを作成しましょう。
余談
認証まわりのテストメソッドとして、TestCaseにて、$this->assertCredentials()
などで使用できるメソッドが多々あります。
特に、例として載せたコードのように、ログインさせないケースがある場合は、assertInvalidCredentials()
が役に立ちました。
Illuminate\Foundation\Testing\ConcernsInteractsWithAuthentication
に、認証回りのテストメソッドがまとまっているので見てみるといいかもです。
最後に
探していてあまり同様の記事が見つからなかったので、今後の自分のメモがてら書いてみました。
間違っている点ありましたら、コメントにてご指摘お願いします。
もしよろしければ、LGTMをよろしくおねがいします~~!!