問題
class User extends Model implements Authenticatable
{
use SoftDeletes;
}
Auth::user() // 論理削除ユーザは除外される
SoftDeletes
を使用している Authenticatable
の場合,グローバルスコープで論理削除を除外するものが付与されるので,認証時にも除外される。ところが論理削除とは違う「有効アカウント」「無効アカウント」といった表現を status={0,1}
のように行っている場合,素の状態では対応できない。ここでは,できるだけ Laravel 標準の Auth に乗っかる形での実装を目指して対応してみる。
(備考)【Laravel】 認証や認可に関する補足資料 - Qiita
実装
AuthScopable
インタフェースの作成
モデルごとに実装は異なるので,共通して使えるようにインタフェースを作成する。命名や引数・返り値の方は Laravel の標準的なスコープの規約に従っている。
<?php
namespace App\Auth;
use Illuminate\Database\Eloquent\Builder;
interface AuthScopable
{
/**
* Add a scope for authentication.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeForAuthentication(Builder $query): Builder;
}
ScopedEloquentUserProvider
の作成
EloquentUserProvider
は Laravel の標準実装にあり,既定ではこれが使用されている。今回はここの一部を継承して,認証でクエリが発行されるときに AuthScopable
で示されるスコープを適用できるようにしてみる。
<?php
namespace App\Auth;
use Illuminate\Auth\EloquentUserProvider;
class ScopedEloquentUserProvider extends EloquentUserProvider
{
/**
* @param null|\Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newModelQuery($model = null)
{
$query = parent::newModelQuery($model);
$instance = $query->getModel();
if ($instance instanceof AuthScopable) {
$query = $instance->scopeForAuthentication($query);
}
return $query;
}
}
AuthServiceProvider
での登録
<?php
namespace App\Providers;
use App;
use App\Auth\ScopedEloquentUserProvider;
use App\Policies;
use App\User;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
App\User::class => Policies\UserPolicy::class,
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$this->registerPolicies();
// EloquentUserProvider をオーバーライド
Auth::provider('eloquent', function (Container $app, array $config) {
return $app->make(ScopedEloquentUserProvider::class, [
'model' => $config['model'],
]);
});
}
}
モデルへの適用
あとは書くだけ!
class User extends Model implements Authenticatable, AuthScopable
{
public function scopeForAuthentication(Builder $query): Builder
{
return $query->where('status', 1);
}
}
Auth::user() // ステータスが 0 のユーザは除外される
おまけ
標準的なスコープ命名規則に従っているので,一応こんな使い方もできます…
(多分そんなに使わないと思うけど)
User::where('email', 'xxx@example.com')->forAuthentication()->firstOrFail()
User::where('email', 'xxx@example.com')->scopes(['forAuthentication'])->firstOrFail()
追記
OSS化しました
mpyw/scoped-auth: Apply specific scope to for user authentication.