12

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.

Hugo A
  • 413
  • 2
  • 6
  • 23

5 Answers5

10

The solution was simple.

First, despite what other tutorials said, you do not have to register the CustomUserProvider in your config providers (Laravel 5.4 here). This was the cause of the errors I was getting.

I only had to override two methods from EloquentUserProvider, retrieveByCredentials(array $credentials) where you return a model based on the credentials provided, and validateCredentials(UserContract $user, array $credentials) where you return a boolean depending on whether credentials are correct.

You can use this same custom provider class with many providers, not just one, e.g.

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

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

After that, when you need to check auth with any provider, you need to provide the provider as guard e.g. Auth::guard('customusers').

Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
Hugo A
  • 413
  • 2
  • 6
  • 23
  • 1
    Thanks! I tried to create a step-by-step answer for a similar problem with your solution: https://stackoverflow.com/a/46224576/2056125 – mhellmeier Sep 14 '17 at 16:53
  • Hey! I'm really new building laravel apps, i'm working on a similar scenario, but i'm confusing on how to override the "retrieveByCredentials" and "validateCredentials", in those methods, should i make the API call to the authentication route and build the user model from the API's Json response? Thanks in advance! – Junior Silva Oct 02 '17 at 20:16
  • @JuniorSilva Happy to help. Why don't you ask that as a question so I can answer there? – Hugo A Oct 03 '17 at 02:02
  • @Bul Ikana, i've just asked a question, i'd be so happy if you could give me a help! Thanks in advance! – Junior Silva Oct 03 '17 at 04:15
5

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
  {

  };
});
Mark Baaijens
  • 496
  • 5
  • 6
3

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.

Mārtiņš Briedis
  • 17,396
  • 5
  • 54
  • 76
  • Can you please provide a link to a 'gist' or 'blog article' with how to properly do it this way. – Julius Moshiro Mar 20 '18 at 10:59
  • There is not more to it. Where are you stuck? 1. Create a custom class that extends EloquentUserProvier 2. Register your user provider to be used as the auth provider by calling Auth::setProvider.. – Mārtiņš Briedis Mar 20 '18 at 14:17
1

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();
                }
            };
        });
    }
Evol Rof
  • 2,528
  • 2
  • 22
  • 37
0

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.

AEG
  • 19
  • 6