›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-dev-login
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Profiles
  • Safety gates
  • The login flow

Extending

  • User resolvers
  • Tenancy
  • Routes and redirects
  • Customizing the page

Reference

  • Configuration
  • Testing
  • Troubleshooting

byrcsc/laravel-dev-login · 1.x

User resolvers.

Change how a profile finds its user when an email lookup on the guard's provider is not enough.

A resolver turns a profile into the user it names. The package ships one, FindUserByEmail, and it is what runs unless you say otherwise.

Reach for a resolver of your own when your users are not found by email address, when a profile should pick a user by role or by recency, or when a development environment should create the user rather than look it up.

What the shipped resolver does

FindUserByEmail asks the profile's guard for its user provider, then calls:

$provider->retrieveByCredentials(['email' => $profile->email]);

Asking the guard rather than a model class or a config key is what makes this work without configuration. Custom user models, several providers behind several guards, and providers that are not Eloquent all resolve through the same call, because it is the provider your application already trusts to find users at login.

A profile on the admin guard is looked up through whatever provider admin names. Nothing about the package needs to know which model that is.

Writing one

Implement ByRcsc\LaravelDevLogin\Contracts\UserResolver:

namespace ByRcsc\LaravelDevLogin\Contracts;

use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;

interface UserResolver
{
    public function resolve(Profile $profile): ?Authenticatable;
}

One method. Return the user, or null to say it does not exist. The caller turns null into ProfileUserNotFound, naming the profile and its email.

Resolvers are built through the container, so constructor injection works:

namespace App\DevLogin;

use App\Models\User;
use ByRcsc\LaravelDevLogin\Contracts\UserResolver;
use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;

final class FindUserByUsername implements UserResolver
{
    public function resolve(Profile $profile): ?Authenticatable
    {
        return User::query()
            ->where('username', $profile->email)
            ->first();
    }
}

The email key is a plain string on the profile. Nothing checks that it looks like an address, so a resolver of your own can read it as a username, an employee number, or anything else.

Choosing which resolver runs

The first of these that is set wins:

WhereApplies to
The profile's resolver keyThat one profile
The resolver key in config/dev-login.phpEvery other profile
Nothing setFindUserByEmail

Set the default for every profile:

// config/dev-login.php
'resolver' => App\DevLogin\FindUserByUsername::class,

Or name one on a single profile:

'oldest' => [
    'label' => 'Whoever was seeded first',
    'email' => 'ignored@example.com',
    'resolver' => App\DevLogin\TheOldestAccount::class,
],

Both places take a class-string. The config file contains no closures anywhere, which is what lets it survive config:cache.

A class-string that does not implement UserResolver throws before the container is asked for it, so the message names the config key or the profile rather than failing further down with an unresolvable dependency.

Ignoring the profile's email entirely

Nothing obliges a resolver to use email. This one takes whoever was seeded first:

namespace App\DevLogin;

use App\Models\User;
use ByRcsc\LaravelDevLogin\Contracts\UserResolver;
use ByRcsc\LaravelDevLogin\Profile;
use Illuminate\Contracts\Auth\Authenticatable;

final class TheOldestAccount implements UserResolver
{
    public function resolve(Profile $profile): ?Authenticatable
    {
        return User::query()->oldest('id')->first();
    }
}

The profile still needs an email, because it is a required key, and this resolver never reads it.

Creating users instead of finding them

The package never writes to your users table. A resolver of yours can:

public function resolve(Profile $profile): ?Authenticatable
{
    return User::firstOrCreate(
        ['email' => $profile->email],
        ['name' => $profile->label, 'password' => bcrypt(Str::random(32))],
    );
}

The trade-off is that you lose the drift detector. ProfileUserNotFound is what tells you a profile and a seeder have gone out of step, and a resolver that creates the user never raises it.

Reading the tenant

A resolver runs after the tenant has been made current, so a tenant-scoped query needs nothing extra. The profile also carries the tenant if you want to branch on it:

public function resolve(Profile $profile): ?Authenticatable
{
    if ($profile->hasTenant()) {
        return User::query()
            ->where('tenant_id', $profile->tenant)
            ->where('email', $profile->email)
            ->first();
    }

    return User::query()->where('email', $profile->email)->first();
}

What to read next

  • The login flow for where resolution sits in the sequence.
  • Tenancy for the resolver that runs before this one.
  • Testing to assert your resolver from a test.
PreviousThe login flowNextTenancy
View source

On this page

  1. What the shipped resolver does
  2. Writing one
  3. Choosing which resolver runs
  4. Ignoring the profile's email entirely
  5. Creating users instead of finding them
  6. Reading the tenant
  7. What to read next