9

Can we use laravel passport with different guards to authenticate APIs for two different types of users. For example we have driver app for driver user and vendor app for vendor user. Both have their different models Driver and Vendor. How can we use different guards to authenticate both types of users using Laravel Passport?

Ahmar Arshad
  • 477
  • 1
  • 10
  • 22
  • Yes, i think you could use Laravel Passport for multi authentication, it's not a build in functionality, but you can code it yourself. – chebaby Oct 17 '18 at 09:27

4 Answers4

9

I managed to create multiple auths (with laravel/passport) by using a simple middlware.

Step 1: config/auth.php

Add your user classes to providers

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

    'api' => [
        'driver' => 'passport',
        'provider' => 'basic_users', // default
    ],        
],

...

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

Clean the cache via CLI

php artisan config:cache

Step 2: Create middleware

php artisan make:middleware AdminUserProvider

Open the newly created middleware in app/Http/Middleware and update the hand method like below

public function handle($request, Closure $next)
{
    config(['auth.guards.api.provider' => 'admin_users']);
    return $next($request);
}

Step 3: Register your middleware

Add the newly created middleware to $routeMiddleware

protected $routeMiddleware = [
    ...
    'auth.admin' => \App\Http\Middleware\AdminUserProvider::class,
];

and make sure it's at the top of $middlewarePriority

protected $middlewarePriority = [
    \App\Http\Middleware\AdminUserProvider::class,
    ...
];

Step 4: Add middleware to route

Route::group(['middleware' => ['auth.admin','auth:api']], function() {

Step 5: LoginControllers (AdminUserController & BasicUserController)

public function login()
{
    $validatedData = request()->validate([
        'email' => 'required',
        'password' => 'required|min:6'
    ]);
    // get user object
    $user = AdminUser::where('email', request()->email)->first();
    // do the passwords match?
    if (!Hash::check(request()->password, $user->password)) {
        // no they don't
        return response()->json(['error' => 'Unauthorized'], 401);
    }
    // log the user in (needed for future requests)
    Auth::login($user);
    // get new token
    $tokenResult = $user->createToken($this->tokenName);
    // return token in json response
    return response()->json(['success' => ['token' => $tokenResult->accessToken]], 200);
}

In summary:

The login controllers use Eloquent models to get the user object and then log the user in through Auth::login($user)

Then for future requests that need authentication, the new middleware will change the api auth guard provider to the correct class.

rharvey
  • 1,987
  • 1
  • 28
  • 23
  • how we can handle the next request if i want to view the profile of admin how we can get the profile of user in next request? – kunal Nov 19 '19 at 05:20
  • You could use the token to determine who the user is via a model but I don't think access tokens are meant to be identifiers. They determine if you have access to something or not. It's more likely you would use this code as your auth layer and then use the token generated to gain access to another service. You can pass in whatever info you want then i.e. POST data. – rharvey Nov 20 '19 at 08:57
  • @rharvey thank your for you solution, But I am getting error like: BadMethodCallException: Call to undefined method App\Merchant::getAuthIdentifierName() in file /home/dimonky/api.example.com/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php on line 50 – mdkamrul Apr 24 '21 at 07:05
  • Hi @mdkamrul, apologies, it's been a long time since I looked at Laravel so I may struggle to help. However, that error suggests that you have a Class called Merchant and it's looking for a method called getAuthIdentifierName which doesn't exist. I assume this is custom code? It might be that you need to check the configs and make sure that you have defined all the relevant classes – rharvey Apr 26 '21 at 13:02
5

Edit: Passport now has support for multiple guard user providers. Please refer the following links for more infos:

Multiple Authentication Guards

Support For Multiple Guards


Old answer (I would not recommend it)

Here is an example of auth.php and api.php to start with

config/auth.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    */

    'guards' => [

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

        'driver-api' => [
            'driver' => 'passport',
            'provider' => 'drivers',
        ],

        'vendor-api' => [
            'driver' => 'passport',
            'provider' => 'vendors',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    */

    'providers' => [

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

        'drivers' => [
            'driver' => 'eloquent',
            'model' => App\Driver::class,
        ],

        'vendors' => [
            'driver' => 'eloquent',
            'model' => App\Vendor::class,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    */

    'passwords' => [

        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        'drivers' => [
            'provider' => 'drivers',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        'vendors' => [
            'provider' => 'vendors',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

routes/api.php

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
*/

Route::group(['namespace' => 'Driver', 'prefix' => 'driver/v1', 'middleware' => 'auth:driver-api'], function() {

    // define your routes here for the "drivers"
});

Route::group(['namespace' => 'Vendor', 'prefix' => 'vendor/v1', 'middleware' => 'auth:vendor-api'], function() {

    // define your routes here for the "vendors"
});

You have to modify this files:

File: vendor\laravel\passport\src\Bridge\UserRepository.php

Copy/Paste getUserEntityByUserCredentials to make a duplicate of it and name it getEntityByUserCredentials

Then, in the new duplicated function, find the below:

$provider = config('auth.guards.api.provider');

And Replace it with:

$provider = config('auth.guards.'.$provider.'.provider');

File: vendor\league\oauth2-server\src\Grant\PasswordGrant.php

in : validateUser method add after $username and $password :

$customProvider = $this->getRequestParameter('customProvider', $request);

if (is_null($customProvider)) {
   throw OAuthServerException::invalidRequest('customProvider');
}

And this instead of the original line

$user = $this->userRepository->getEntityByUserCredentials(
    $username,
    $password,
    $this->getIdentifier(),
    $client,
    $customProvider
);

After doing this you'll be able to pass an extra key/value pair to your access token request, like for example:

grant_type => password,
client_id => someclientid
client_secret => somesecret,
username => someuser,
password => somepass,
client_scope => *,
provider => driver-api // Or vendor-api

I hope this will be helpful for you

chebaby
  • 7,362
  • 50
  • 46
  • Thank you so much for for this solution. But I could not understand the last part grant_type => password, client_id => someclientid client_secret => somesecret, username => someuser, password => somepass, client_scope => *, provider => driver-api // Or vendor-api – Ahmar Arshad Oct 17 '18 at 12:49
  • How can we retrieve the token for the logged in driver or vendor? – Ahmar Arshad Oct 17 '18 at 12:55
  • @AhmarArshad it's where you "Requesting Tokens", please see the docs https://laravel.com/docs/5.6/passport#password-grant-tokens – chebaby Oct 17 '18 at 14:15
  • 2
    Instead change in "vendor" folder, any solution rather – Harat Jan 21 '20 at 07:07
  • @Harat What do you think about having another customPassport folder and create required files and extends the original files. Then in your composer.json, dont-discover part add in "laravel/passport", and then register your own CustomPassportServiceProvider in your app.php – Edward Chew Aug 23 '20 at 13:44
  • @EdwardChew great idea but now i found another solution try laravel sanctum. – Harat Aug 24 '20 at 09:53
  • It gives me undefined method getEntityByUserCredentials – sd077 Dec 25 '20 at 19:06
0

After spent time I have found that in Laravel 7 there is no custom code required except some configuration. For details please check this answer I have tested & implemented in my projects Multi Auth with Laravel 5.4 and Passport

Rejoanul Alam
  • 5,435
  • 3
  • 39
  • 68
0

You don't necessarily need to change config for each request. You need a client for each guard. After creating clients by running

passport:install

Make sure you specified provider field in database. It should be same value as auth.providers config.

After creating clients and guards, use following code when creating an access token.

App::clearResolvedInstance(ClientRepository::class);
app()->singleton(ClientRepository::class, function () {
    return new ClientRepository(User::CLIENT_ID, null); // Client id of the model
});

Make sure you specified provider in oauth_clients table.

Parsa_Gholipour
  • 794
  • 5
  • 16