4

In Laravel 9, I am trying to hit the login API with the custom guard client, I am getting the following error. Please help.

BadMethodCallException: Method Laravel\Passport\Guards\TokenGuard::attempt does not exist.

config/Auth.php

    'guards' => [
        ...
        'client' => [
            'driver' => 'passport',
            'provider' => 'client',
        ],
    ],

    'providers' => [
        ...
        'client' => [
            'driver' => 'eloquent',
            'model' => App\Models\Client::class,
        ],
    ],

Error line: if(!$authGuard->attempt($login)){

api/AuthController.php

public function login(Request $request){
        $login = $request->validate([
            'email' => 'required|string',
            'password' => 'required|string',
        ]);
        try {
            $authGuard = Auth::guard('client');
            if(!$authGuard->attempt($login)){
                $data = 'Invalid Login Credentials';
                $code = 401;
            } else {
                $user = $authGuard->user();
                $token = $user->createToken('user')->accessToken;
                $code = 200;
                $data = [
                    'user' => $user,
                    'token' => $token,
                ];
            }
        } catch (Exception $e) {
            $data = ['error' => $e->getMessage()];
        }
        return response()->json($data, $code);
    }

Models/Client.php

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class Client extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

Screenshot: enter image description here

Muhammad Owais
  • 980
  • 3
  • 17
  • 37

4 Answers4

2

I think Auth::attempt() is not compatible with passport.

So, you can use Auth::check() method instead.

John Kage
  • 74
  • 5
2

attempt() is only available to guards implementing the StatefulGuard interface.

So i agree with John that attempt is not compatible with Passport.

You can try this it should work :

auth()->guard('client')->setUser($login); or Auth::guard('client')->setUser($login);

  • I tried the second one, It's giving me this error `Laravel\Passport\Guards\TokenGuard::setUser(): Argument #1 ($user) must be of type Illuminate\Contracts\Auth\Authenticatable, array given`, please advice – Muhammad Owais Sep 21 '22 at 06:43
  • Hi can you please post the full error ? – adydcoder15 Sep 21 '22 at 06:57
  • Here it is, `TypeError: Laravel\Passport\Guards\TokenGuard::setUser(): Argument #1 ($user) must be of type Illuminate\Contracts\Auth\Authenticatable, array given, called in /app/Http/Controllers/Api/v1/admin/AuthController.php on line 60 in file vendor/laravel/framework/src/Illuminate/Auth/GuardHelpers.php on line 91` – Muhammad Owais Sep 21 '22 at 07:00
  • I think there is one more issue if you see your $login variable it has used for validating also you are using same for the attempt can you give it shot like below : `Auth::guard('client')->attempt(['email' => $request->email, 'password' => $request->password])` – adydcoder15 Sep 21 '22 at 07:15
  • I tried with both, using `attempt` and `setUser`, e.g: `Auth::guard('clients')->setUser(['email' => $request->email, 'password' => $request->password])` , Its returning same – Muhammad Owais Sep 21 '22 at 07:21
  • can you add a snap of what controller looks like after the changes .. ? – adydcoder15 Sep 21 '22 at 07:24
  • I've added the screenshot in the question, please have a look – Muhammad Owais Sep 21 '22 at 07:28
  • @MuhammadOwais It should be like this : `public function login(Request $request) { try{ if (!Auth::guard('client')->attempt(['email' => $request->email, 'password' => $request->password])) { return response(['status' => 'Invalid Credentials']); } } catch (Exception $e) { $data = ['error' => $e->getMessage()]; } return response()->json($data,$code); }` – adydcoder15 Sep 21 '22 at 07:36
  • the issue didn't solve actually, you are again using `attempt` which doesn't support with the passport guards. also I tried changing with `setUser` but still same issue `TypeError: Laravel\Passport\Guards\TokenGuard::setUser(): Argument #1 ($user) must be of type ` – Muhammad Owais Sep 21 '22 at 07:43
  • Hi @MuhammadOwais which version of Laravel you are using and also what is the version of Passport .I Just attempted with fresh installation and that is working well. – adydcoder15 Sep 21 '22 at 09:18
  • @MuhammadOwais There is no sense of changing the guard to session in this case you are not even using passport.Please refer any tutorial. As the above proposed solutions are already working if that make sense to you then you would not have downgraded it – adydcoder15 Sep 21 '22 at 09:52
  • Hello, I am using laravel 9, This is what I followed https://laracasts.com/discuss/channels/laravel/laravel-6-custom-guard-issue?page=1&replyId=545950 – Muhammad Owais Sep 21 '22 at 13:36
1

I solved it by changing the driver from passport to session in config/auth.php

'clients' => [
    'driver' => 'session',
    'provider' => 'clients',
],

I am not sure this is the correct solution, but it works.

Please feel free to post the answer if there is any better solution

Thanks

Muhammad Owais
  • 980
  • 3
  • 17
  • 37
0

it seems that Laravel Passport doesn't support this type of authentication style, what I did to solve this was a bit weird but it works. I made one endpoint for all user model types like this :

 Route::post('auth/{user_type}/login', [AuthController::class, 'login']);

Then I added a helper function to get the user model I wished to authenticate against :

function resolve_user_type(string $user_type) 
{
    return match ($user_type) {
        'user'=> User::class,
        'admin'=> Admin::class,
        default => throw new InvalidArgumentException()
    };
}

then I modified your login method like this :

public function login(Request $request , $user_type)
{
    $model = resolve_user_type($user_type);
    $login = $request->validate([
        'email' => 'required|email',
        'password' => 'required|string',
    ]);
    try {
        $user = $model::whereEmail($login['email'])->first();

        if (!$user || !Hash::check($login['password'], $user->password)) {
            $data = 'Invalid Login Credentials';
            $code = 401;
        } else {

            $token = $user->createToken('authToken', [$user_type])->accessToken;
            $code = 200;
            $data = [
                'user' => $user,
                'token' => $token,
            ];
        }
    } catch (Exception $e) {
        $data = ['error' => $e->getMessage()];
    }
    return response()->json($data, $code);
} 

where the second parameter in the method createToken() was the name of the scope I created for the user token and defined it in the AuthServiceProvider file in the boot method :

Passport::tokensCan([
        'user'=>'user',
        'admin'=>'admin'
    ]);

this was dynamic enough for me to use and move on, but remember these three must be the same :

  • user type in the route
  • user type in the token scope
  • user type in the helper function