Passport: Passport Multi-Auth

Created on 27 Oct 2016  路  80Comments  路  Source: laravel/passport

Would passport be implementing Multi-Auth system? or is there another way to incorporate the multi-auth into the app.

I have created Multiple Models which use Laravel's Auth system to implement proper multi-auth system. I'm not sure how to use passport for the same.

enhancement

Most helpful comment

I managed to get access tokens from different auth providers in laravel, but I had to modify:


File: vendorlaravelpassportsrcBridgeUserRepository.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: vendorleagueoauth2-serversrcGrantPasswordGrant.php

  • Find Line: 94
  • Update with the following code:
        $user = $this->userRepository->getEntityByUserCredentials(
            $username,
            $password,
            $this->getIdentifier(),
            $client,
            $provider
        );

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 => api

In this example, I set provider to 'api', which is default. But now I can pass other auth providers as part of the request. So, just to show another example, I could do the following:

grant_type => password,
client_id => someclientid
client_secret => somesecret,
username => someuser,
password => somepass,
client_scope => *,
provider => api_admins

Now I used 'api_admins' auth provider to authenticate tokens.

P.S:
I replaced the functions with new ones instead of overwriting because there was another third-party library involved leagueoauth2-server, besides the laravel/passport library. If I were to recommend a proper way to implement this would be to extend the getUserEntityByUserCredentials function so that it could accept our new argument, but for that you would have to overwrite the interface function definition too, it's located at vendor\league\oauth2-server\src\Repositories\RepositoryInterface.php

Hope this helps someone else

All 80 comments

I am also looking for same. Anyone here to help?

https://github.com/laravel/passport/blob/7ed1a0b80c1da9a3bf25eb180068ddd005a82068/src/Bridge/UserRepository.php#L35

This package always uses users provider model in config/auth to authenticate, so I guess there are no hacks for now?

+1

With #216 , it is dynamic now so multiple guards can be added and used with passport now I believe.

Still we cannot implement multi-auth using laravel using this update.

I managed to get access tokens from different auth providers in laravel, but I had to modify:


File: vendorlaravelpassportsrcBridgeUserRepository.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: vendorleagueoauth2-serversrcGrantPasswordGrant.php

  • Find Line: 94
  • Update with the following code:
        $user = $this->userRepository->getEntityByUserCredentials(
            $username,
            $password,
            $this->getIdentifier(),
            $client,
            $provider
        );

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 => api

In this example, I set provider to 'api', which is default. But now I can pass other auth providers as part of the request. So, just to show another example, I could do the following:

grant_type => password,
client_id => someclientid
client_secret => somesecret,
username => someuser,
password => somepass,
client_scope => *,
provider => api_admins

Now I used 'api_admins' auth provider to authenticate tokens.

P.S:
I replaced the functions with new ones instead of overwriting because there was another third-party library involved leagueoauth2-server, besides the laravel/passport library. If I were to recommend a proper way to implement this would be to extend the getUserEntityByUserCredentials function so that it could accept our new argument, but for that you would have to overwrite the interface function definition too, it's located at vendor\league\oauth2-server\src\Repositories\RepositoryInterface.php

Hope this helps someone else

@zubair1 zubair1 I have tried your method and it worked, let me rewrite the steps more clearly,

1) File: vendorlaravelpassportsrcBridgeUserRepository.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.'.$theNewProvider.'.provider');

  • After that add $theNewProvider to getEntityByUserCredentials arguments as follow:

    public function getEntityByUserCredentials($username, $password, $grantType, 
    ClientEntityInterface $clientEntity, $theNewProvider)
    

2)File: vendorleagueoauth2-serversrcGrantPasswordGrant.php

  • In the function validateUser update with the following code:

    $user = $this->userRepository->getEntityByUserCredentials(
        $username,
        $password,
        $this->getIdentifier(),
        $client,
        $theNewProvider
    
  • add the following variable to the same function:

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

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

3) Try it with Postman with the following:

url: http://127.0.0.1:8000/oauth/token
Headers:
Content-Type: application/json

body:

{
"username":"[email protected]",
"password":"secret",
"grant_type": "password",
    "client_id": 2,
    "client_secret": "string",
    "theNewProvider": "admin"
}

Do not forget to do the following as well:

  • Update the model, for example in the admin model add the following

    use Laravel\Passport\HasApiTokens;
    class Admin extends Authenticatable
    {
         use Notifiable,HasApiTokens;
    
  • In auth.php:

    'guards' => [
    'admin' => [
        'driver' => 'passport',
        'provider' => 'admins',
    ],
    

Nothing new until now?

I have made a very simple hack to do it:

1) Add a new custom user provider in config/auth.php using a model that extends Authenticatable class and use HasRoles, HasApiTokens traits.

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

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

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

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

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

        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Administrator::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

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

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

];

2) Create a new custom middleware PassportCustomProvider like that:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Config;

class PassportCustomProvider
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $params = $request->all();
        if (array_key_exists('provider', $params)) {
            Config::set('auth.guards.api.provider', $params['provider']);
        }
        return $next($request);
    }
}

3) Register the middleware as a route middleware:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'passport-administrators' => \App\Http\Middleware\PassportCustomProvider::class,
    ];
}

4) Encapsulate the passport routes with this middleware in AuthServiceProvider:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Route::group(['middleware' => 'passport-administrators'], function () {
            Passport::routes();
        });
    }

}

5) Add the 'provider' param in your request at /oauth/token:

POST /oauth/token HTTP/1.1
Host: localhost
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache

{
    "username":"[email protected]",
    "password":"password",
    "grant_type" : "password",
    "client_id": "client-id",
    "client_secret" : "client-secret",
    "provider" : "admins"
}

The problem after this step is the token saved in oauth_access_tokens only contains the administrator ID. When we use the token received to authenticate, it not look at Administrator models.

Complementing the above comment, to make it functional for Bearer tokens it's necessary the following steps:

6) Create a migration to save the relationship between access tokens and providers:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class OauthAccessTokenProviders extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('oauth_access_token_providers', function (Blueprint $table) {
            $table->string('oauth_access_token_id', 100)->primary();
            $table->string('provider');
            $table->timestamps();

            $table->foreign('oauth_access_token_id')
                ->references('id')->on('oauth_access_tokens')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('oauth_access_token_providers');
    }
}

7) Add a event listener in EventServiceProvider:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'Laravel\Passport\Events\AccessTokenCreated' => [
            'App\Listeners\PassportAccessTokenCreated',
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    }
}

8) Put this following code in your listener, to save the relationship between access tokens and providers:

<?php

namespace App\Listeners;

use App\Events\Laravel\Passport\Events\AccessTokenCreated;
use Carbon\Carbon;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PassportAccessTokenCreated
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  \Laravel\Passport\Events\AccessTokenCreated $event
     * @return void
     */
    public function handle(\Laravel\Passport\Events\AccessTokenCreated $event)
    {
        $provider = \Config::get('auth.guards.api.provider');
        DB::table('oauth_access_token_providers')->insert([
            "oauth_access_token_id" => $event->tokenId,
            "provider" => $provider,
            "created_at" => new Carbon(),
            "updated_at" => new Carbon(),
        ]);
    }
}

9) Create a new global middleware to handle the custom providers setup for each request:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\DB;
use League\OAuth2\Server\ResourceServer;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;

class PassportCustomProviderAccessToken
{

    private $server;

    public function __construct(ResourceServer $server)
    {
        $this->server = $server;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $psr = (new DiactorosFactory)->createRequest($request);

        try {
            $psr = $this->server->validateAuthenticatedRequest($psr);
            $token_id = $psr->getAttribute('oauth_access_token_id');
            if ($token_id) {
                $access_token = DB::table('oauth_access_token_providers')->where('oauth_access_token_id',
                    $token_id)->first();

                if ($access_token) {
                    \Config::set('auth.guards.api.provider', $access_token->provider);
                }
            }
        } catch (\Exception $e) {

        }

        return $next($request);
    }
}

10) Finally, register your middleware as global HTTP middleware at app/Http/Kernel

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\PassportCustomProviderAccessToken::class
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'passport-custom-provider' => \App\Http\Middleware\PassportCustomProvider::class
    ];
}

It's only missing the refresh token and the cookie login handles.

@zubair1 Thanks Zubair your solution worked.

@MohammedSabbah - Glad it worked for you and thanks for simplifying the steps. I hope it helps others out.

@ghulamali2612 - You're welcome, glad it helped :)

@zubair1, @MohammedSabbah, @ghulamali2612

Your Solution Works, i can get access token for Admin model, but when i test it in post man http://localhost:8000/api/user i'm getting the user of User model not the Admin models uses

https://stackoverflow.com/questions/44903323/laravel-api-returning-different-model-user

thank you

@sanprodev In config/auth.php under guards -> api change the provider to Admin
'api' => [ 'driver' => 'passport', 'provider' => 'admin ', //in the link provided it's client ],

@renanwilliam i've followed your step and managed to get multi auth passport working.

but i found another problem when using javascript client, i always get unauthenticated error for Admin auth.
i use Consuming Your API with Javascript (section from laravel 5.4 docs) and have added LaravelPassportHttpMiddlewareCreateFreshApiToken::class to the new guard but still no luck.

anyone facing same problem here ?

Hi @rockmantist.

The token was not validated in PassportCustomProviderAccessToken middleware?:

try {
      $psr = $this->server->validateAuthenticatedRequest($psr); //is it validated here?
      $token_id = $psr->getAttribute('oauth_access_token_id');
      if ($token_id) {
         $access_token = DB::table('oauth_access_token_providers')->where('oauth_access_token_id', 
         $token_id)->first();

         if ($access_token) {
             \Config::set('auth.guards.api.provider', $access_token->provider);
          }
      }
} catch (\Exception $e) { }

Hi.. thanks 4 all... but what about when passport make any changes to their code.. and you made a composer update? It will overwrite all your files.. and your app will be down. Im right?!

@matiazar using the approach that I described above there's no changes in passport code

Thanks @MohammedSabbah it worked for login. But facing issue with posting data. Will there a change in token passing while posting data?

Hi @rockmantist does it worked for you?

@renanwilliam sorry for the late reply, using the middleware above didn't work. i end up creating a new middleware, it's basically the exact same as CreateFreshApiToken but i modified it to force using my other guard (admin).

the only problem now is, it still authenticated on another guard, for example i perform authentication on guard web, get the token, try to authenticate to admin guard, it's authenticated (i guess it's because both have the same row with same id or primary key value).

@rockmantist does the guard means auth()->guard('admin')->user() here, I am searching for a solution and just reached here.

@Fahad032 in my case it's guard auth()->guard('auth:api')->user() for frontend and auth()->guard('auth:api_admin')->user() for admin.

+1
Same problem here, can't use passport for other provider

What about being able to change login or impersonate other users with passport? I have one app that needs to make requests in the name of the user and I can't use their access tokens since the value is different on the database, and I also can't use Auth::once or Auth::onceById while in the auth:api middleware.
The user doesn't login to that App, but I still need the app to be able to execute actions to my API just like if it was the user.

For example, just checking a user in my api with that app:

  • Endpoint: /v2/user?id=12345
    The request would be made with my app's access token and then a custom middleware would check if it was my app and check if there was an id in the request. If there was an Id then it would change the login for the next actions (ex: go to controllers) with the user's Id.

So in the end, that endpoint would show the info from user 12345 instead of my app's account info.

Laravel passport is only working with User as a provider. Even if you fetch token by adding above changes Post and get requests are not working with changed passport provider than User.

Better you create multi auth with session driver and let 'User' model only for passport.
Repo here https://github.com/storesbuzz/laravel-multiAuth

@storesbuzz The above approach with provider parameter works fine with all the CRUD methods.i have implemented in 2 projects which are in production.

anyone see a simpler solution for just the laravel_token cookie functionality of Passport (https://laravel.com/docs/5.5/passport#consuming-your-api-with-javascript) as I don't need the whole Oauth token functionality.

Laravel_token auth to my api works for my regular users but I dont think I can get the laravel_token cookie added for my multiauth 'admins' that use a different guard provider and model without doing something really ugly, I'm hoping for this functionality only I can do something cleaner.

@renanwilliam I think the PassportCustomProviderAccessToken kernel class middlewareGroups inside the best bar
'api' => [
聽聽聽聽聽聽聽聽聽聽聽聽 App Http Middleware PassportAccessTokenValidate :: class,
聽聽聽聽聽聽聽聽聽聽聽聽 'throttle: 60,1',
聽聽聽聽聽聽聽聽聽聽聽聽 'bindings',
聽聽聽聽聽聽聽聽 ],

Hi @sowork ,

I use some routes that are not only in api so that's the reason to keep globally

@renanwilliam Yes, but your idea is really a good idea

Thanks @sowork! I will try create a package to make it work's painless

I made a package based on responses from @renanwilliam to solve this. I hope it helps!

Hello, I also created a small package for this: https://github.com/jsdecena/laravel-passport-mutiauth Hope it helps :)

Still not working

@sfelix-martins Use your package and if you generate access tokens but when I use a postman to obtain the user's data, 404 error appears, I also wanted to ask where or how to configure this part:

  • On routes encapsulated with custom-provider middleware you needs now pass the 'api' guard to user() method:
    public function store(Request $request)
    {
    $entity = $request->user('api');
    ...

    }

Your answer would be very helpful, thank you

@SamitoX4 When you use the middleware group defined in your app/Http/Kernel encapsulating your Passport::routes. e.g:

class AuthServiceProvider extends ServiceProvider
{
    ...

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
        // Encapsulated access token routes
        Route::group(['middleware' => 'custom-provider'], function () {
            Passport::routes(function ($router) {
                return $router->forAccessTokens();
            });
        });
    }
    ...
}

The middleware will be configure the guard api with provider defined on route to create an access_token: config(['auth.guards.api.provider' => $accessToken->provider])

For this reason you can pass the guard to obtains the entity authenticated. For example:

To get the user logged using the Illuminate\Http\Request you need the guard api:

  • app/Http/Kernel.php
        'api' => [
            'throttle:60,1',
            'bindings',
            'cors',
            \SMartins\PassportMultiauth\Http\Middleware\AddCustomProvider::class,
            \SMartins\PassportMultiauth\Http\Middleware\ConfigAccessTokenCustomProvider::class,
        ],
  • Your api routes.
Route::get('/company', 'CompanyController@me')->middleware('api');
  • Your controller
use App\Company;
use Illuminate\Http\Request;

class CompanyController extends Controller
{
    public function me(Request $request)
    {
        // To get authenticatable entity using the guard
        $entity = $request->user('api')

        // If your access token passed to your `/company` route was created with `provider` param `companies`, for example, your entity is an instance of `Company`
        return $entity instanceof Company // true
    }
}

You can use still the facade Auth to get the authenticatable entity passing the guard like in the previous sample:

use App\Company;
use Illuminate\Http\Request;
use Auth;

class CompanyController extends Controller
{
    public function me(Request $request)
    {
        // To get authenticatable entity using the guard
        $entity = Auth::guard('api')->user();

        // If your access token passed to your `/company` route was created with `provider` param `companies`, for example, your entity is an instance of `Company`
        return $entity instanceof Company // true
    }
}

I hope it helps!

@sfelix-martins Thank you very much for your response, I'll see how it goes! ... good day.

@SamitoX4 if you wish you can create an issue on package repository. Good day.

@zubair1 Thank you! your solution is working. Do you know any way to test using phpunit?
I tried:
Passport::actingAs(factory('App\Admin')->create(), [], 'admin-api');
result error: Expected status code 200 but received 401.

@jaysongyn I don't know that if you already solved your problem to unit test, but I tested here any possibilities:

If you set the guard of user logged ('admin-api' in your case) you need pass too the same guard on method that your auth middleware. Example:

Route::group(['middleware' => 'auth:admin-api'], function ($request) {
    return $request->user(); // or passing the guard `$request->user('admin-api');
}

All these hacks and fixes are irrelevant. We should be writing PRs to update the original package rather then work-arounds. Thats the idea behind open source.

@andrewmclagan still waiting your PR ...
@sfelix-martins thanks for your cool package

Any news here? Passport multiauth feature like TymonJWTAuth it does is a very useful and necessary feature for more complex projects.
So far, a important feature for me, porting TymonJWTAuth to passport (see https://stackoverflow.com/questions/41376928/laravel-5-3-passport-jwt-authentication) and let code like this still working:

if (!$token = auth()->guard('api-admin')->attempt($credentials)) {
    return response()->json(['message' => 'Invalid credentials'], 422);
}

I have done this using this technique:

My Admin API Url: https://example.com/api/login
My Customer API Url: https://example.com/api.customer/login

adding this to any ServiceProvider (I have added in RouteServiceProvider.php before Customer custom route)
` // Fix/Support for multiple user with different table by changing provider on customer-api circumstances

    Config::set('auth.guards.api.provider', request()->input('provider', starts_with(request()->path(), 'api.customer') ? 'customers' : 'users'));`

and must add your custom provider in providers array in config/auth.php
'customers' => [ 'driver' => 'eloquent', 'model' => App\Customer::class, ],

I solved this by using a forked version of Passport and middleware. The middleware allows me to select the provider based on route:

public function handle($request, Closure $next, $provider = null)
    {
        if ($provider) {
            $apiProvider = config('auth.guards.api.provider');

            config()->set('auth.providers.' . $apiProvider . '.model', config('auth.providers.' . $provider . '.model'));
        }

        return $next($request);
    }
Route::middleware('provider:athletes')->group(function() {
    Route::middleware('auth:api')->group(function () {
        Route::get('authenticated-stuff', 'Api\ApiController@authenticatedStuff');
    });

    Route::get('guest-stuff', 'Api\ApiController@guestStuff');
});

I've then stored all access tokens, codes etc as polymorphic relationships in the database, e.g.

'user_type' => (new $model)->getMorphClass(),

This required edits to BridgeAccessTokenRepository, BridgeAuthCodeRepository, ClientRepository, TokenRepository and HasApiTokens.

I feel that this has given me the most control.

Config::set('auth.guards.api.provider', 'provider')

Only API access is normal, but when the user accesses using guard(web), the provider ofguard(web) cannot be updated normally.

->middleware('auth:api,web') Config::set('auth.guards.{$api|$web}.provider', 'provider') {$api|$web} Unable to distinguish between currently used guard

any news about this feature ?

I used @zubair1 method to handle multi auth with passport. first I cloned passport repository and league/oauth2-server repository . updated them as needed . changed composer config to use the forks .
and updated my packages. then the login working well .

but the main problem is with the tokens relationships with the user table . how to fix this ?

Can this provider be stored in access_token

Hi @renanwilliam, I followed your steps, but it says class PassportCustomProviderAccessToken already in use, perhaps do you know where I missed? Or can you hint about how to find clashing class names?

I'm using passport 7.0
Thanks!

Hi @prasetyaputraa ,
I have written these steps using the passport 3.0, probably may have some breaking changes.
But if it already in use you can take another name of your choose, just replace all references in another steps.

Hello! When I test a POST in Postman passing username, password and provider, I'm receiving the message "Personal access client not found. Please create one.". I didn't understand what I need do. Can you help me, please?

@ReneGustavo Try one of the following channels:

Any news? I hesitate, using any "workaround" when maybe the functionality will be implemented and force me to change my code. Maybe it made sense, vote for one of the implementations like https://github.com/sfelix-martins/passport-multiauth for integration?
I know some scenarios where multiauth would be very useful and over 50 comments in that discussion here show also, that there is a need for this feature.

Okay, maybe one point for discussion: So far I see, the sfelix-martins solution use same /oauth/token for requesting tokens for different auth users with "provider" parameter.
I think, a expected behaviour or setup is, using different urls for different auth (tables). But using a additional parameter would made the solution incompatible with other oauth client implementations. Something like /oauth/token and /admin/oauth/token I think would made more sense.

is there any chance to get this feature in the near future?

My solution for Multi-Auth:

  1. Create middleware (app/Http/Middleware/SetPassportAuthGuard.php)
<?php

namespace App\Http\Middleware;

use Closure;

class SetPassportAuthGuard
{
    public function handle($request, Closure $next, $guard = 'api')
    {
        app('config')->set('auth.passport.guard', $guard); // save current guard name in config
        return $next($request);
    }
}
  1. Register route middleware (in app/Http/Kernel.php)
protected $routeMiddleware = [
    //
    'passport' => \App\Http\Middleware\SetPassportAuthGuard::class,
    //
];
  1. Add routes (app/routes/api.php)
// route for issue tokens
Route::group(['middleware' => ['passport:myguard1']], function() {
    Route::post('/token1', '\Laravel\Passport\Http\Controllers\AccessTokenController@issueToken');
});

// resource route
Route::group(['middleware' => ['auth:myguard1']], function() {
    Route::get('test1', function(Request $request) {
        return response()->json($request->user());
    });
});
  1. Register alias in app/config/app.php to replace Laravel\Passport\Bridge\UserRepository with your own implementation
'aliases' => [
    //
    'Laravel\Passport\Bridge\UserRepository' => App\PassportUserRepository::class,
    //
]
  1. New UserRepository
<?php

namespace App;

use Illuminate\Auth\EloquentUserProvider;
use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;

class PassportUserRepository implements UserRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
    {
        $guard = config('auth.passport.guard'); // obtain current guard name from config
        $provider = config('auth.guards.'.$guard.'.provider');
        $userProvider = app('auth')->createUserProvider($provider);

        if ($userProvider instanceof EloquentUserProvider &&
            method_exists($model = $userProvider->getModel(), 'findForPassport')) {
            $user = (new $model)->findForPassport($username);
        } else {
            $user = $userProvider->retrieveById($username);
        }

        if (!$user) {
            return;
        }

        if (method_exists($user, 'validateForPassportPasswordGrant')) {
            if (!$user->validateForPassportPasswordGrant($password)) {
                return;
            }
        } else {
            if (!$userProvider->validateCredentials($user, ['password' => $password])) {
                return;
            }
        }

        if ($user instanceof UserEntityInterface) {
            return $user;
        }

        return new User($user->getAuthIdentifier());
    }
}

Many thanks to you @afilippov1985, I finally get it working with your solution that I think it's the most elegant way to implement custom passport guards. This said, I just wanted to expose my solution because I needed custom username/email column and get it working with passport proxy.

  1. Create middleware (app/Http/Middleware/SetPassportAuthGuard.php)
<?php

namespace App\Http\Middleware;

use Closure;

class SetPassportAuthGuard
{
    public function handle($request, Closure $next, $guard = '')
    {
        app('config')->set('auth.passport.guard', $guard);

        return $next($request);
    }
}
  1. Register route middleware (app/Http/Kernel.php)
protected $routeMiddleware = [
    //
    'passport' => \App\Http\Middleware\SetPassportAuthGuard::class,
    //
];
  1. Add middleware in routes (app/routes/xxx.php)
/*
|--------------------------------------------------------------------------
| AUTH
|--------------------------------------------------------------------------
|
*/

Route::group(['prefix' => 'auth', 'middleware' => ['passport:admin-api']], function ()
{
    Route::post('/login', 'AuthController@login');
    Route::post('/refresh-token', 'AuthController@refreshToken');
});

/*
|--------------------------------------------------------------------------
| LOGGED IN ROUTES
|--------------------------------------------------------------------------
|
*/

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

    Route::post('/fetch/something/protected', 'SomeController@fetchSomething');

    //
});

If you use a passport proxy like me, do the following, otherwise skip to step 5.

4a. Add middleware in AuthServiceProvider (app/Providers/AuthServiceProvider.php)

public function boot()
{
    //

    Route::group(['middleware' => ['passport:'.request()->guard]], function ()
    {
        Passport::routes(function ($router) {
            $router->forAccessTokens();
            $router->forTransientTokens();
        });
    });

    //
}

4b. Add guard in passport proxy

$response = $http->post(app_url('/oauth/token'), ['form_params' => array_merge($data, [
    'client_id' => (string) env('PASSWORD_CLIENT_ID'),
    'client_secret' => (string) env('PASSWORD_CLIENT_SECRET'),
    'grant_type' => $grant_type,
    'guard' => 'admin-api',
    'scope' => '',
])]);
  1. Create PassportUserRepository (app\OAuth\PassportUserRepository.php)
<?php

namespace App\OAuth;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Hashing\Hasher;
use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;

class PassportUserRepository implements UserRepositoryInterface
{
    /**
     * The hasher implementation.
     *
     * @var \Illuminate\Contracts\Hashing\Hasher
     */
    protected $hasher;

    /**
     * Create a new repository instance.
     *
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
     * @return void
     */
    public function __construct(Hasher $hasher)
    {
        $this->hasher = $hasher;
    }

    /**
     * {@inheritdoc}
     */
    public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
    {
        $guard = config('auth.passport.guard');
        $provider = config('auth.guards.'.$guard.'.provider');

        if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
            throw new RuntimeException('Unable to determine authentication model from configuration.');
        }

        if (method_exists($model, 'findForPassport')) {
            $user = (new $model)->findForPassport($username);
        } else {
            $user = (new $model)->where(config('auth.providers.'.$provider.'.passport.username') ?: 'email', $username)->first();
        }

        if (! $user) {
            return;
        } elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
            if (! $user->validateForPassportPasswordGrant($password)) {
                return;
            }
        } elseif (! $this->hasher->check($password, $user->getAuthPassword())) {
            return;
        }

        return new User($user->getAuthIdentifier());
    }
}
  1. Register alias in app/config/app.php to replace Laravel\Passport\Bridge\UserRepository
'aliases' => [
    //
    'Laravel\Passport\Bridge\UserRepository' => App\OAuth\PassportUserRepository::class,
    //
]

I also had to composer dump-autoload after adding this.

  1. Configure guards (app/config/auth.php)
'guards' => [
    'web-api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
    'admin-api' => [
        'driver' => 'passport',
        'provider' => 'admin_users',
    ],
],

'passport' => [
    'guard' => '',
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Model\User::class,
        'passport' => [
            'username' => 'email',
        ],
    ],
    'admin_users' => [
        'driver' => 'eloquent',
        'model' => App\Model\AdminUser::class,
        'passport' => [
            'username' => 'username',
        ],
    ],
],

@drpiou You can define findForPassport method in your user models. It is more clean way.

// App\Model\User
public function findForPassport($username)
{
    return $this->where('email', $username)->first();
}

// App\Model\AdminUser
public function findForPassport($username)
{
    return $this->where('username', $username)->first();
    // may be also cheking is_admin flag
    // return $this->where('username', $username)->where('is_admin', 1)->first();
}

Thanks very much @drpiou, I followed your procedure and I still get "Unauthenticated" when I protected my api with the middleware('admin-api'). Can you please help me out?

I have followed @afilippov1985 steps and it is still not working for me.

Hi @renanwilliam I followed your steps, Admin registration working good and i got token. while use Admin login i got UnAuthorised error only.

This is my Api url and input for login in postman
http://localhost/api/admin/login

{
"email":"[email protected]",
"password":xxxxxxxx,
}
In AdminController this is my login function.

public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];

    if (auth()->attempt($credentials)) {
        $token = auth()->user()->createToken('Admin')->accessToken;
        return response()->json(['token' => $token], 200);
    } else {
        return response()->json(['error' => 'UnAuthorised'], 401);
    }
}

This is my routes/api.php

Route::post('admin/login', 'AdminController@login');
Route::post('admin/register', 'AdminController@register');

Please help.

Hi @riyas1012 ,

The solution that I described don't have these steps that you are mentioning.
I have used the standard passport route /oauth/token and middlewares to get it working.

The easiest way to add multi-auth into passport would be to add middleware to check the provider.

app/Http/Middleware/CheckProvider.php

<?php

namespace App\Http\Middleware;

use Closure;

class CheckProvider
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $validator = validator()->make($request->all(), [
            'provider' => 'in:admins'
        ]);

        if ($validator->fails()) {
            return response()->json([
                'error' => 'invalid_provider',
                'message' => 'This provider is invalid.'
            ], 422);
        }

        if ($request->input('grant_type') === 'password' && $request->input('provider')) {
            config(['auth.guards.api.provider' => $request->input('provider')]);
        }

        return $next($request);
    }
}

app/Http/Kernel.php

protected $routeMiddleware = [
        ...
        'provider' => \App\Http\Middleware\CheckProvider::class,
    ];

app/Providers/AuthServiceProvider.php

public function boot()
    {
        ...
        Passport::routes(null, ['middleware' => 'provider']);
    }

config/auth.php

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

'providers' => [
        ...
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
    ],

'passwords' => [
        ...
        'admins' => [
            'provider' => 'admins',
            'table' => 'password_resets',
            'expire' => 60
        ],
    ],

app/Admin.php

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens, Notifiable;
    //
}

routes/api.php

Route::middleware('auth:admins')->get('/admin', function (Request $request) {
    return Auth::user();
});

Now just pass provider with the value admins with the rest of the payload to /oauth/token. This will authenticate against the Admin model and return a valid JWT. If your Admin model doesn't have an 'email' field, you can add this to your model:

public function findForPassport($username)
{
    return $this->where('username', $username)->first();
}

Hi @riyas1012 ,

The solution that I described don't have these steps that you are mentioning.
I have used the standard passport route /oauth/token and middlewares to get it working.

@renanwilliam Sorry its my mistake. Now i got it Thanks.

@afilippov1985 : After access token expiration how it will give a new access token for that user.

@drpiou : As per your code there is no need to add the provider in the request always right?

@msankar1991
After access token expiration you will get 401 http error
Then you should use refresh token to retrive new access token / refresh token pair

POST https://example.org/api/your-token-endpoint
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=def50200c6...long...string...

or get new access token / refresh token pair passing user credentials

POST https://example.org/api/your-token-endpoint
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=password&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&username=USER&password=PASSWORD

Then re-send request using fresh access token

@afilippov1985 Thanks for the updates. And one more clarification each every api request need to authenticate user identity so this case I'm passing to access_token with _Authorization_ header always. But how can I validate this token is valid or not.

Hi @renanwilliam ,
How to identify whether its admin or user by token?

Hi @renanwilliam ,
How to identify whether its admin or user by token?

https://github.com/laravel/passport/issues/161#issuecomment-308564114

@afilippov1985

i used Auth::guard('admin')->attempt(['username' => $username, 'password' => $password]);
have this error
Method Illuminate\Auth\RequestGuard::attempt does not exist.

This code will work for session guard driver, but not for passport. You can't do manual authentication this way with Passport.

@afilippov1985
My back-end code `
$loginResult = Auth::guard('admin')->attempt(['username' => $username, 'password' => $password]);

    if($loginResult) {

        $result['token'] = $user->createToken('FullStack')->accessToken;

        return $this->success('ok','',$result);

    }`

Front end send request like this Authorization: Bearer +token ,but unable to authenticate

This code will work for session guard driver, but not for passport. You can't do manual authentication this way with Passport.

I have changed session guard driver

Hello everyone. Due to the fact that this thread is receiving lots of comments on how to circumvent this or do an alternative implementation and not discuss the feature at hand I've decided to lock the thread. Please use a support channel if you wish to discuss these things.

We're still open to prs if anyone wants to take a stab at implementing this. If you do, please provide a thorough explanation of the changes and tests. If you're introducing breaking changes please target master.

New issue that discusses a possible implementation can be found here: https://github.com/laravel/passport/issues/982

For anyone still interested in the feature please take a look at the proposed PR https://github.com/laravel/passport/pull/1220 and provide feedback on it, thanks.

PR for multi guard is merged on master. You all can thank @billriess for getting this in.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SwiTool picture SwiTool  路  3Comments

brryfrmnn picture brryfrmnn  路  3Comments

seriousjelly picture seriousjelly  路  3Comments

cookiejarblush picture cookiejarblush  路  4Comments

gbgelado picture gbgelado  路  3Comments