13

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 5

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').

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks! I tried to create a step-by-step answer for a similar problem with your solution: stackoverflow.com/a/46224576/2056125
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!
@JuniorSilva Happy to help. Why don't you ask that as a question so I can answer there?
@Bul Ikana, i've just asked a question, i'd be so happy if you could give me a help! Thanks in advance!
6

When you don't want to create a separate class file, customizations of EloquentUserProvider can be done by using an anonymous class inside the boot method of your service provider.

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;

Auth::provider('eloquent', function($app, array $config) {
    return new class($app['hash'], $config['model']) extends EloquentUserProvider
    {
        // override any public methods here, e.g.
        public function validateCredentials(Authenticatible $user, array $credentials)
        {
            // some custom validation
        }
    };
});

Comments

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.

2 Comments

Can you please provide a link to a 'gist' or 'blog article' with how to properly do it this way.
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..
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();
                }
            };
        });
    }

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.