#auth #development #laravel #php

Imagine you have a Laravel web application with different types of users. Let's you can have both internal as well as external users. Let's also assume that for logging, depending on which guard is used, you want to either allow only internal or exteral users to login. There might be places where you want to allow both.

Shouldn't be hard, except if you happen to have a global scope that filters out all external users. So when you try to login as an external user, you get a ModelNotFoundException because the global scope filters out all external users.

Let's first take a look at how the global scope was configured.

In my apps, I prefer to define them as a class and then add them to the model in the booted method. This way, I can easily reuse them or find them.

app/Scopes/OnlyInternalUsersScope.php

 1namespace App\Scopes;
 2
 3use Illuminate\Database\Eloquent\Builder;
 4use Illuminate\Database\Eloquent\Model;
 5use Illuminate\Database\Eloquent\Scope;
 6
 7final class OnlyInternalUsersScope implements Scope
 8{
 9    public function apply(Builder $builder, Model $model): void
10    {
11        $builder->where('type', 'internal');
12    }
13}

app/Models/User.php

 1namespace App\Models;
 2
 3use App\Scopes\OnlyInternalUsersScope;
 4
 5final class User extends Authenticatable
 6{
 7    protected static function booted(): void
 8    {
 9        self::addGlobalScope(new OnlyInternalUsersScope());
10    }
11}

At this point, whenever you try to find a user, it will only return users with the type internal.

To configure the authentication, we need to take some extra steps. The first thing I did is to create a class that extends EloquentUserProvider.

It's implementation is pretty simple. It just overrides the retrieveById method and removes the global scope. Doing so will enable it to search for all users, regardless of their type.

app/Providers/AllUsersProvider.php

 1namespace App\Providers;
 2
 3use App\Scopes\OnlyInternalUsersScope;
 4use Illuminate\Auth\EloquentUserProvider;
 5
 6class AllUsersProvider extends EloquentUserProvider
 7{
 8    public function retrieveById($identifier)
 9    {
10        return $this->createModel()
11            ->newQuery()
12            ->withoutGlobalScope(OnlyInternalUsersScope::class)
13            ->find($identifier);
14    }
15}

The next step is to add a new auth provider to the AuthServiceProvider. In my scenario, I called it external and uses the provider which we just created.

app/Providers/AuthServiceProvider.php

 1namespace App\Providers;
 2
 3use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
 4use Illuminate\Support\Facades\Auth;
 5
 6class AuthServiceProvider extends ServiceProvider
 7{
 8    public function boot()
 9    {
10        Auth::provider('all-users', function ($app, $config) {
11            return new AllUsersProvider($app['hash'], $config['model']);
12        });
13    }
14}

The final step is to configure the auth guards and providers. In the example below, I've added the external guard and a provider called "all-users". The web guard is the default one and is used for internal users. The all-users guard is used for external and internal users and uses the driver we have just defined.

config/auth.php

 1return [
 2    'guards' => [
 3        'web' => [
 4            'driver' => 'session',
 5            'provider' => 'internal-users',
 6        ],
 7
 8        'all-users' => [
 9            'driver' => 'session',
10            'provider' => 'all-users',
11        ],
12    ],
13
14    'providers' => [
15        'internal-users' => [
16            'driver' => 'eloquent',
17            'model' => App\User::class,
18        ],
19
20        'all-users' => [
21            'driver' => 'all-users',
22            'model' => App\User::class,
23        ],
24    ],
25];

inspired by a post on laracasts.com