Apply specific scope for user authentication.
- PHP:
^8.0
- Laravel:
^9.0 || ^10.0
Via Composer
$ composer require mpyw/scoped-auth
For Fortify users
Warning
Default Fortify's RedirectIfTwoFactorAuthenticatable
implementation directly uses internal Model
under UserProvider
, however, the Laravel author won't be willing to fix it for whatever reason. So we need to configure Fortify like this:
CustomFortifyAuthenticator.php
<?php
namespace App\Auth;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Auth\UserProvider;
use Laravel\Fortify\Fortify;
class CustomFortifyAuthenticator
{
private const PASSWORD_NAME = 'password';
private readonly UserProvider $provider;
public function __construct(StatefulGuard $guard)
{
// Assert `StatefulGuard` has `getProvider()` which is not declared in the contract
assert(method_exists($guard, 'getProvider'));
$provider = $guard->getProvider();
assert($provider instanceof UserProvider);
$this->provider = $provider;
}
public function __invoke(Request $request): ?Authenticatable
{
$user = $this->provider->retrieveByCredentials([
Fortify::username() => $request->input(Fortify::username()),
]);
return $user && $this->provider->validateCredentials($user, [
self::PASSWORD_NAME => $request->input(self::PASSWORD_NAME),
]) ? $user : null;
}
}
AuthServiceProvider.php
<?php
namespace App\Providers;
use App\Auth\CustomFortifyAuthenticator;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;
class AuthServiceProvider extends ServiceProvider
{
public function boot(CustomFortifyAuthenticator $authenticator): void
{
Fortify::authenticateUsing($authenticator);
}
}
Via PHPUnit
$ composer test
Implement AuthScopable contract on your Authenticatable Eloquent Model.
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Mpyw\ScopedAuth\AuthScopable;
class User extends Model implements UserContract, AuthScopable
{
use Authenticatable;
public function scopeForAuthentication(Builder $query): Builder
{
return $query->where('active', 1);
}
}
<?php
use Illuminate\Support\Facades\Auth;
$user = Auth::user(); // Only include users where "active" is 1
Note that you can reuse another existing scope.
public function scopeActive(Builder $query): Builder
{
return $query->where('active', 1);
}
public function scopeForAuthentication(Builder $query): Builder
{
return $this->scopeActive($query);
}
As a by-product, you can also run scope queries based on the standard Eloquent way.
$user = User::where('email', 'xxx@example.com')->forAuthentication()->firstOrFail();
$user = User::where('email', 'xxx@example.com')->scopes(['forAuthentication'])->firstOrFail();
Licensed under the MIT License. See License File for more information.