-
Notifications
You must be signed in to change notification settings - Fork 781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Passport Multi-Auth #161
Comments
I am also looking for same. Anyone here to help? |
passport/src/Bridge/UserRepository.php Line 35 in 7ed1a0b
This package always uses users provider model in |
+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: vendor\laravel\passport\src\Bridge\UserRepository.php
File: vendor\league\oauth2-server\src\Grant\PasswordGrant.php
After doing this you'll be able to pass an extra key/value pair to your access token request, like for example:
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:
Now I used 'api_admins' auth provider to authenticate tokens. P.S: Hope this helps someone else |
@zubair1 zubair1 I have tried your method and it worked, let me rewrite the steps more clearly,
2)File: vendor\league\oauth2-server\src\Grant\PasswordGrant.php
body:
Do not forget to do the following as well:
|
Nothing new until now? |
I have made a very simple hack to do it:
<?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,
],
],
];
<?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);
}
}
<?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,
];
}
<?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();
});
}
}
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:
<?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');
}
}
<?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();
}
}
<?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(),
]);
}
}
<?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);
}
}
<?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 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 |
@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. anyone facing same problem here ? |
Hi @rockmantist. The token was not validated in 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 |
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 For example, just checking a user in my api with that app:
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. |
@storesbuzz The above approach with |
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 |
@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? |
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. |
is there any chance to get this feature in the near future? |
My solution for Multi-Auth:
<?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);
}
}
protected $routeMiddleware = [
//
'passport' => \App\Http\Middleware\SetPassportAuthGuard::class,
//
];
// 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());
});
});
'aliases' => [
//
'Laravel\Passport\Bridge\UserRepository' => App\PassportUserRepository::class,
//
]
<?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.
If you use a passport proxy like me, do the following, otherwise skip to step 5. 4a. Add middleware in AuthServiceProvider (
4b. Add guard in passport proxy
I also had to
|
@drpiou You can define // 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 { public function login(Request $request)
This is my routes/api.php Route::post('admin/login', 'AdminController@login'); Please help. |
Hi @riyas1012 , The solution that I described don't have these steps that you are mentioning. |
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 public function findForPassport($username)
{
return $this->where('username', $username)->first();
} |
@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
or get new access token / refresh token pair passing user credentials
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 , |
|
@msankar1991 |
i used |
This code will work for |
@afilippov1985
Front end send request like this |
I have changed |
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: #982 |
For anyone still interested in the feature please take a look at the proposed PR #1220 and provide feedback on it, thanks. |
PR for multi guard is merged on master. You all can thank @billriess for getting this in. |
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.
The text was updated successfully, but these errors were encountered: