15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel #2Advent Calendar 2019

Day 11

【Laravel】 認証でユーザを取得するとき,特定のスコープに限定する

Last updated at Posted at 2019-12-09

問題

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.

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?