How to add a custom User Provider in Laravel 5.4

Viewed 18222

I have a Laravel 5.4 app, in which I have to authenticate my admin users from an external API, which when successfully logged in it returns a JSON with user information.

I am creating a custom guard to make this:

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users'
        ],

        'custom' => [
            'driver' => 'session',
            'provider' => 'customusers'
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],

and this is my custom provider:

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

    'customusers' => [
        'driver' => 'jsonresponse',
        'model' => App\Admin::class,
    ]
],

After that, I am not sure how to continue. I've read some tutorials like the one from George Buckingham, and I have created a custom User provider (Right now I just need it to extend from EloquentUserProvider, eventually I will override some functions to connect to the API)

<?php

namespace App\Providers;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Support\Str;

class CustomUserProvider extends EloquentUserProvider { 

}

then registered it both in App\Providers\AuthServiceProvider

public function boot()
{
    $this->registerPolicies();

    Auth::provider('jsonresponse', function($app, array $config) {
        return new CustomUserProvider($app['hash'], $config['model']);
    });
}

and in config/app.php

'providers' => [

    // Lots of other providers

    // Own providers
    App\Providers\CustomUserProvider::class,
],

But after that, I get the following error:

Argument 1 passed to Illuminate\Auth\EloquentUserProvider::__construct() must be an instance of Illuminate\Contracts\Hashing\Hasher, instance of Illuminate\Foundation\Application given, called in /var/www/public/iberorides/vendor/laravel/framework/src/Illuminate/Foundation/Application.php on line 612 and defined

If I override the constructor of my CustomUserProvider and change the params in the closure in AuthServiceProvider, I get the following error:

Argument 1 passed to Illuminate\Foundation\Application::bootProvider() must be an instance of Illuminate\Support\ServiceProvider, instance of App\Providers\IberoUserProvider given, called in /var/www/public/iberorides/vendor/laravel/framework/src/Illuminate/Foundation/Application.php on line 771 and defined

This makes me think I am not doing things the right way.

Could someone put me in the right direction?

Thank you so much.

5 Answers

Very small customizations of the EloquentUserProvider can be done by means of the following code inside the boot method of your AuthServiceProvider.

Auth::provider('eloquent', function($app, array $config)
{
  return new class($app['hash'], $config['model']) extends \Illuminate\Auth\EloquentUserProvider
  {

  };
});

None of the solutions I found quite worked for me, so I figured out a super easy way. (Laravel 5.4)

My problem was that I had to allow users to log in with their phone number too.

I override the EloquentUserProvider:

class PhoneUserProvider extends EloquentUserProvider
{
    public function retrieveByCredentials(array $credentials)
    {
        if (!$credentials) {
            return null;
        }

        // Fallback to original email authorization
        if (filter_var($credentials['email'], FILTER_VALIDATE_EMAIL)) {
            return parent::retrieveByCredentials($credentials);
        }

        // Assume we are logging in with a phone number
        return UserModel::where('phone', $credentials['email'])->first();
    }
}

And in my AppServiceProvider boot methode I added these lines:

Auth::setProvider(new PhoneUserProvider(app(Hasher::class), UserModel::class));

You can even set provider in login controller, based on some conditions, etc.

In case it is of any help to others looking for a way to customize the retrieveByCredentials method, it is possible to do so without implementing a custom UserProvider. The EloquentUserProvider simply takes a credentials array and builds out a User query using where clauses for each key/value pair in the array. So if you already have a class that is defining those credentials, you can simply customize there.

In my case, I had a ResetPasswordController that implemented ResetsPasswords. I was looking to modify how the user was selected during submission (using an id and password, rather than email and password), so I overrode the credentials method, replacing email with ID_Contact like so:

/**
 * Get the password reset credentials from the request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
protected function credentials(Request $request)
{
    return $request->only(
        'password',
        'password_confirmation',
        'token',
        'ID_Contact'
    );
}

There were additional places that needed to be customized within the ResetsPasswords implementation in order to bring my particular use case into practice (plus the password reset form itself). Each scenario would be a little different when taking this approach. But this may point some in a helpful direction, depending on your particular requirements.

as @Mark Baaijens say

In your AuthServiceProvider

public function boot()
    {
        $this->registerPolicies();

        Auth::provider('eloquent', function($app, array $config) {
            return new class ($app['hash'], $config['model']) extends EloquentUserProvider {

                public function retrieveById($identifier)
                {
                    $model = $this->createModel();

                    $condition = [$model->getAuthIdentifierName() => $identifier];


                    if ($model instanceof User) {
                        $condition['custom_more_filed'] = 1;
                    }

                    return $model->newQuery()
                                 ->where($condition)
                                 ->first();
                }
            };
        });
    }
Related