I am trying to setup multi auth with Laravel Passport, but it doesn't seem to support it. I am using the Password Grant to issue tokens which requires me to pass username/password of the user wanting access tokens.
I have 3 auth guards/providers setup, 4 in total.
Users, Vendors, Admins and API
2 of the Auths need passport access, so each user needs to be able to issue tokens. But Passport automatically takes the API auth provider, but I want this to change based on which user is logging in.. If user does then the User provider and if its a vendor then the Vendor provider.
But the way Passport currently only supports only 1 user type, so its defaulting to the API provider.
Is there something better for this? or should I go with roles based authentication instead.
If you still need.
I prefer go with roles, there is an amazing plugin for that: https://github.com/larapacks/authorization
But if you somehow needs that, you will be able to use following the steps bellow.
For multi guards, you will have to overwrite some code.
Instead of loading PassportServiceProvider, you create your own and extends the PassportServiceProvider and overwrites the method makePasswordGrant.
On this method, you will change the Passport UserRepository for your own repository extended. On user repository you must to change the static model config for a dynamic one (I load from request attributes, but you can get from anywhere).
You may have to overwrite something else, but I made a test and works.
Ex:
PassportServiceProvider
namespace App\Providers;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant\PasswordGrant;
use Laravel\Passport\PassportServiceProvider as BasePassportServiceProvider;
use Laravel\Passport\Passport;
class PassportServiceProvider extends BasePassportServiceProvider
{
/**
* Create and configure a Password grant instance.
*
* #return PasswordGrant
*/
protected function makePasswordGrant()
{
$grant = new PasswordGrant(
$this->app->make(\App\Repositories\PassportUserRepository::class),
$this->app->make(\Laravel\Passport\Bridge\RefreshTokenRepository::class)
);
$grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn());
return $grant;
}
}
UserRepository
namespace App\Repositories;
use App;
use Illuminate\Http\Request;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use Laravel\Passport\Bridge\UserRepository;
use Laravel\Passport\Bridge\User;
use RuntimeException;
class PassportUserRepository extends UserRepository
{
/**
* {#inheritdoc}
*/
public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity)
{
$guard = App::make(Request::class)->attributes->get('guard') ?: 'api';
$provider = config("auth.guards.{$guard}.provider");
if (is_null($model = config("auth.providers.{$provider}.model"))) {
throw new RuntimeException('Unable to determine user model from configuration.');
}
if (method_exists($model, 'findForPassport')) {
$user = (new $model)->findForPassport($username);
} else {
$user = (new $model)->where('email', $username)->first();
}
if (! $user ) {
return;
} elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
if (! $user->validateForPassportPasswordGrant($password)) {
return;
}
} elseif (! $this->hasher->check($password, $user->password)) {
return;
}
return new User($user->getAuthIdentifier());
}
}
PS: Sorry my bad english.
You have to modify the main library Files.
1) File: vendor\laravel\passport\src\Bridge\UserRepository.php
Find getUserEntityByUserCredentials and Copy the complete method and Paste this method below with name getEntityByUserCredentials. Donot modify the main function because it is used somewhere.
//Add the $provider variable at last or replace this line.
public function getEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity, $provider)
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');
2) File: vendor\league\oauth2-server\src\Grant\PasswordGrant.php
In the function validateUser add the below code after line no. 88
$provider = $this->getRequestParameter('provider', $request);
if (is_null($provider)) {
throw OAuthServerException::invalidRequest('provider');
}
After adding replace the following code with
$user = $this->userRepository->getEntityByUserCredentials(
$username,
$password,
$this->getIdentifier(),
$client,
$provider
);
Now try this using postman
Add the provider field in your input field like
provider = api_vendors
OR
provider = api_admins
OR
provider = api_users
And so on....
make sure you have added your provider and set the drivers in the config/auth.php
'guards' => [
'api_admins' => [
'driver' => 'passport',
'provider' => 'admins',
],
'api_vendors' => [
'driver' => 'passport',
'provider' => 'vendors',
],
I hope this helps.
I have created a small package for this issue. Here's the link for the complete doc link
But the gist is, whenever a user entity gets logged in, it checks for the guards and providers.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'customers' => [
'driver' => 'passport',
'provider' => 'customers'
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => 'App\User',
],
/**
* This is the important part. You can create as many providers as you like but right now,
* we just need the customer
*/
'customers' => [
'driver' => 'eloquent',
'model' => 'App\Customer',
],
],
You should have a controller like this:
<?php
namespace App\Http\Controllers\Auth;
use App\Customers\Customer;
use App\Customers\Exceptions\CustomerNotFoundException;
use Illuminate\Database\ModelNotFoundException;
use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
use Psr\Http\Message\ServerRequestInterface;
use Lcobucci\JWT\Parser as JwtParser;
class CustomerTokenAuthController extends AccessTokenController
{
/**
* The authorization server.
*
* #var \League\OAuth2\Server\AuthorizationServer
*/
protected $server;
/**
* The token repository instance.
*
* #var \Laravel\Passport\TokenRepository
*/
protected $tokens;
/**
* The JWT parser instance.
*
* #var \Lcobucci\JWT\Parser
*/
protected $jwt;
/**
* Create a new controller instance.
*
* #param \League\OAuth2\Server\AuthorizationServer $server
* #param \Laravel\Passport\TokenRepository $tokens
* #param \Lcobucci\JWT\Parser $jwt
*/
public function __construct(AuthorizationServer $server,
TokenRepository $tokens,
JwtParser $jwt)
{
parent::__construct($server, $tokens, $jwt);
}
/**
* Override the default Laravel Passport token generation
*
* #param ServerRequestInterface $request
* #return array
* #throws UserNotFoundException
*/
public function issueToken(ServerRequestInterface $request)
{
$body = (parent::issueToken($request))->getBody()->__toString();
$token = json_decode($body, true);
if (array_key_exists('error', $token)) {
return response()->json([
'error' => $token['error'],
'status_code' => 401
], 401);
}
$data = $request->getParsedBody();
$email = $data['username'];
switch ($data['provider']) {
case 'customers';
try {
$user = Customer::where('email', $email)->firstOrFail();
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => $e->getMessage(),
'status_code' => 401
], 401);
}
break;
default :
try {
$user = User::where('email', $email)->firstOrFail();
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => $e->getMessage(),
'status_code' => 401
], 401);
}
}
return compact('token', 'user');
}
}
and the request should be:
POST /api/oauth/token HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
grant_type=password&username=test%40email.com&password=secret&provider=customers
To check in your controller who is the logged in user, you can do:
auth()->guard('customers')->user()
I have done it in Laravel 7 without any custom code as suggested other answers. I have just changed 3 file as follows
config/auth.php file (My new table name is doctors)
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
'api-doctors' => [
'driver' => 'passport',
'provider' => 'doctors',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'doctors' => [
'driver' => 'eloquent',
'model' => App\Doctor::class,
],
],
Revise my Doctor model similarly as User model (App/Doctor.php)
....
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class Doctor extends Authenticatable
{
use HasApiTokens, Notifiable;
Finally define routes using middleware routes/api.php file as follows
//normal middleware which exist already
Route::post('/choose', 'PatientController#appointment')->middleware('auth:api');
//newly created middleware provider (at config/auth.php)
Route::post('/accept', 'Api\DoctorController#allow')->middleware('auth:api-doctors');
Now when you will create new oauth client you may use artisan passport:client --password --provider this command which prompt you in command line for choosing table
before that do not forget to run
php artisan config:cache
php artisan cache:clear
Also you can create user manually in oauth_clients table by replacing provider column value users to doctors
Some hints at reference link
https://laravel.com/docs/7.x/passport#customizing-the-user-provider
We are waiting for Laravel to add this feature to its package but for those who want to add this feature, I suggest using this package
Laravel passport is only working with User as a provider. Even if you fetch token by adding above changes with PasswordGrant & UserRepository, while you go for API call for Post and get requests are not working with changed passport provider other than User.
Better you create multi auth with session driver if must needed as Vendors and Customers. let 'User' model only for passport whose table columns supports admin, API, vendor, etc.
Repo here laravel-multiAuth
if you look for solution to the Passport Multi-Auth API
I recommend you this solution
Related
i'm trying to develop a multi tenant, app in laravel, with multiple DBs and subdomains, so far i'm using the default user guard for authenticating in the main domain let's say it's example.com, it works fine, i'm also using a different guard for the subdomains, registration works fine, but the login seems to be broken, it authenticates the user but if i try to Auth:user() or even redirect to a protected route it looks like the user has already logged out.
I'm using relational database as the session driver (to avoid subdomains user to modify the cookies domain and access other subdomains), the sessions seems to be stored correctly in the sessions table of the main domain, but in the subdomain every record has the user_id set as null.
Laravel 8.28.1
PHP 7.4.12
Multi tenancy by https://tenancyforlaravel.com
Here is my config/auth.php file
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
// this is the guard for subdomains
'collaboratore' => [
'driver' => 'session',
'provider' => 'collaboratori',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'collaboratori' => [
'driver' => 'eloquent',
'model' => App\Models\Collaboratore::class,
],
this is my model for users in the subdomains
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Collaboratore extends Authenticatable
{
use HasFactory, Notifiable;
protected $table = 'collaboratore';
protected $guard = 'collaboratore';
public $primaryKey = 'id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'username',
'password',
'email',
// ... other stuff ...
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
and this is my controller for users in the subdomains
public function login(Request $request )
{
// validate request
$credentials = $this->validate($request, [
'email' => 'required|email',
'password' => 'required'
]);
if ( Auth::guard('collaboratore')->attempt( $credentials ) )
{
// login successful
return redirect('/home');
}
//dd("failed");
// login failed
return $request->expectsJson()
? response([ 'message' => 'Invalid credentials', 401 ])
: redirect()->back()->withInput($request->only('email', 'remember'));
}
any help would be appreciated, i'm kinda stuck right now
From Laravel website https://laravel.com/docs/8.x/authentication#introduction
The attempt method is normally used to handle authentication attempt's
from your application's "login" form. If authentication is successful,
you should regenerate the user's session to prevent session fixation:
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
So you should add $request->session()->regenerate(); inside your if attempt.
it looks like i managed to solve his, the problem was here, i changed
public function __construct()
{
$this->middleware('guest')->except('logout');
}
to this
public function __construct()
{
$this->middleware('web');
}
and now it's working, but the session is still note being stored
First things first, I am using Hyn-Multi Tenant in my laravel 6 application Where there is a central database [connection = system] handles multiple tenant database. So far this package has helped me a lot but my application needs passport implementation for apis which is not documented in the package.
However there are other tutorials which claim passport implementation on Hyn package. I followed them and able to create access token per tenant user.
This is my config/auth.php:
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'system-users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'system',
],
'staff' => [
'driver' => 'session',
'provider' => 'staff',
],
'api' => [
'driver' => 'passport',
'provider' => 'staff',
'hash' => false,
],
'student' => [
'driver' => 'passport',
'provider' => 'student',
'hash' => false,
],
],
'providers' => [
'system' => [
'driver' => 'eloquent',
'model' => App\Models\System\User::class,
],
'staff' => [
'driver' => 'eloquent',
'model' => App\Models\Tenant\Staff::class,
],
'student' => [
'driver' => 'eloquent',
'model' => App\Models\Tenant\Student::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
My each tenant models uses UsesTenantConnection trait
This is my EnforceTenancy middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Config;
class EnforceTenancy
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
Config::set('database.default', 'tenant');
return $next($request);
}
}
This is my AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Passport::routes(null, ['middleware' => 'tenancy.enforce']);
// FOLLOWING CODE IS HAVING PROBLEM
//Passport::useTokenModel(OAuthAccessToken::class);
//Passport::useClientModel(OAuthClient::class);
//Passport::useAuthCodeModel(OAuthCode::class);
//Passport::usePersonalAccessClientModel(OAuthPersonalAccessClient::class);
$this->commands([
\Laravel\Passport\Console\InstallCommand::class,
\Laravel\Passport\Console\ClientCommand::class,
\Laravel\Passport\Console\KeysCommand::class,
]);
\Laravel\Passport\Passport::tokensExpireIn(\Carbon\Carbon::now()->addMinutes(10));
\Laravel\Passport\Passport::refreshTokensExpireIn(\Carbon\Carbon::now()->addDays(1));
}
So far all good, now I am going to explain in points,
When I call createToken('MyApp') I am able to generate token on tenant db, for example:
if (Auth::guard('staff')->attempt(['email' => $request->email, 'password' => $request->password])) {
$user = Auth::guard('staff')->user();
$auth_tokens = $user->createToken('MyApp');
$access_token = $auth_tokens->accessToken;
...
}
but to access login protected apis, I am sending bearer access token in header
window.axios
.get("/api/meta",{
headers: fetchAuthHeaders()
})
.then(response => {
if(true == response.data.status) {
var data = response.data.data;
this.school.name = data.school_meta.name;
this.school.logo = data.school_meta.logo;
} else{
alert(response.data.message);
}
})
api.php
Route::domain('{hostname}.lvh.me')->group(function () {
Route::middleware('tenant.exists')->group(function () {
Route::get('/get-oauth-secret', 'Tenant\MetaController#getOAuthData');
Route::post('validate-login','Tenant\AuthController#validateLogin');
Route::middleware(['auth:api'])->group(function (){
Route::get('meta','Tenant\AuthController#getMetaData'); //this api
});
});
});
I am getting response as {"message":"Unauthenticated."}
Once the token is generated in step 1, I copy this token and paste into postman's header section and uncomment the custom passport models in AuthServiceProvider.php as shown below
AuthServiceProvider.php
public function boot()
{
...
// UNCOMMENTED FOLLOWING CUSTOM PASSPORT MODELS
Passport::useTokenModel(OAuthAccessToken::class);
Passport::useClientModel(OAuthClient::class);
Passport::useAuthCodeModel(OAuthCode::class);
Passport::usePersonalAccessClientModel(OAuthPersonalAccessClient::class);
...
}
Now I can access api/meta route but while login and creating token I am getting error:
ErrorException: Trying to get property 'id' of non-object in file /home/winlappy1/Desktop/multi_tenancy/vendor/laravel/passport/src/PersonalAccessTokenFactory.php on line 98
I just want to know where I am going wrong, I know my explanation is quite ambiguous and confusing but thats all how I can explain my issue. I am ready to provide more clarification but I need to resolve this issue.
Try to add
\App\Http\Middleware\EnforceTenancy::class
into the beginning of $middlewarePriority array in Kernel.php
Also use Laravel Passport 9.1.0 which support multi Auth
Try to do this
#AuthServiceProvider
Add this
public function boot()
{
$this->registerPolicies();
This one is to check if the database is Tenant or not
$website = \Hyn\Tenancy\Facades\TenancyFacade::website();
if ($website != null) {
Passport::useClientModel(PassportClient::class);
Passport::useTokenModel(PassportToken::class);
Passport::useAuthCodeModel(PassportAuthCode::class);
Passport::usePersonalAccessClientModel(PassportPersonalAccessClient::class);
}
$this->commands([
\Laravel\Passport\Console\InstallCommand::class,
\Laravel\Passport\Console\ClientCommand::class,
\Laravel\Passport\Console\KeysCommand::class,
]);
\Laravel\Passport\Passport::tokensExpireIn(\Carbon\Carbon::now()->addMinutes(10));
\Laravel\Passport\Passport::refreshTokensExpireIn(\Carbon\Carbon::now()->addDays(1));
}
Along with these add The four models
Like this
Create four Models Which enforce the Tenants
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\AuthCode;
class PassportAuthCode extends AuthCode
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\Client;
class PassportClient extends Client
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\PersonalAccessClient;
class PassportPersonalAccessClient extends PersonalAccessClient
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\Token;
class PassportToken extends Token
{use UsesTenantConnection;}
Also use (tenancy.enforce) middleware Enforcetenancy
'tenancy.enforce' => \App\Http\Middleware\EnforceTenancy::class, $Routemiddleware kernel.php
EnforceTenancy.php middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
class EnforceTenancy
{
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
Config::set('database.default', 'tenant');
return $next($request);
}
}
Force the tenant routes through tenancy.enforce middleware
Also publish the new migrations and migrate:fresh as new fields are added to the new passport
I'm new to Laravel and i created two guards "user guard"(default one) and "admin guard". And i'm saving auth sessions in database instead of file.
Now the problem is user id is causing conflict in sessions table.
For example, if i create a new user in users table and new admin account in admin table both would have same id in sessions table and since the id is not unique it's automatically logging me in to admin account even though if i just login as normal user.
I've already searched on Google but couldn't find anything useful. Only this guy has asked same question but not working answer:
Multi session tables in Laravel
Here's my code:
config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
]
],
AdminLoginController.php
if (auth()->guard('admin')->attempt(['email' => $request->email, 'password' => $request->password])) {
$user = auth()->guard('admin')->user();
return redirect()->route('admin');
}
There probably shouldn't be a separate table and separate eloquent model for an admin, as an admin is a User. They are just a User with elevated permissions.
Instead of creating a separate model, guard, and provider for admins, consider attaching roles and/or permissions to your User model to track if a User is an administrator.
Here's a good package to help with that: https://github.com/spatie/laravel-permission
I'm not sure it's conflicting. I believe you just don't use the guards correctly.
You need to make sure, you never call Auth::user() if you're logging in as Admin. You should always need to use Auth::guard('admin')->user().
extend the laravel default DatabaseSessionHandler and override the addUserInformation function
<?php
namespace App\Extensions;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Session\DatabaseSessionHandler;
class CustomDatabaseSessionHandler extends DatabaseSessionHandler
{
/**
* Add the user information to the session payload.
*
* #param array $payload
* #return $this
* #throws BindingResolutionException
*/
protected function addUserInformation(&$payload): static
{
if ($this->container->bound(Guard::class)) {
info(($this->user() ? get_class($this->user()) : null));
$payload['userable_type'] = $this->user() ? get_class($this->user()) : null;
$payload['userable_id'] = $this->userId();
}
return $this;
}
/**
* Get the currently authenticated user's ID.
*
* #return mixed
* #throws BindingResolutionException
*/
protected function user(): mixed
{
return $this->container->make(Guard::class)->user();
}
}
then register the driver in AppServiceProvider
public function boot(): void
{
Session::extend('custom-database', function ($app) {
$table = $app['config']['session.table'];
$lifetime = $app['config']['session.lifetime'];
$connection = $app['db']->connection($app['config']['session.connection']);
return new CustomDatabaseSessionHandler($connection, $table, $lifetime, $app);
});
}
change your sessions table migration
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('userable_type')->nullable();
$table->foreignId('userable_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
then change session driver in your .env file
SESSION_DRIVER=custom-database
now the sessions stored in the database are polymorphic
So I definitely can't wrap my head around this one. I'm following a Laravel 5.2 tutorial here.
http://blog.damirmiladinov.com/laravel/laravel-5.2-socialite-facebook-login.html#.V2gUIrgrJPY
And getting the error listed above in the title. My routes look like this:
Route::get('/', function () {
if(Auth::check()) return view('auth/register');
return view('auth/login');
});
Route::get('/redirect', 'MailAuthController#redirect');
Route::get('/callback', 'MailAuthController#callback');
Controller looks like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Socialite;
class MailAuthController extends Controller
{
//
public function redirect()
{
return \Socialite::with('microsoft')->redirect();
}
public function callback()
{
// when microsoft calls with token
}
public function user()
{
}
}
And services.php looks like this:
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Stripe, Mailgun, Mandrill, and others. This file provides a sane
| default location for this type of information, allowing packages
| to have a conventional place to find your various credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
],
'mandrill' => [
'secret' => env('MANDRILL_SECRET'),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),
'region' => 'us-east-1',
],
'sparkpost' => [
'secret' => env('SPARKPOST_SECRET'),
],
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
'microsoft' => [
'client_id' => env('MICROSOFT_CLIENT_ID'),
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
'redirect' => env('http://localhost:8000/callback'),
],
];
And other than that I have no idea where I might be going wrong. Light my way!
I would recommend using the Microsoft Graph provider from the Socialite Providers package.
Pull in the Microsoft-Graph provider via your composer.json file:
"require": {
...
"laravel/socialite": "^2.0",
"socialiteproviders/microsoft-graph": "dev-master"
},
Run composer update.
Next, add the connection credentials to config/services.php:
...
'graph' => [
'client_id' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxx',
'redirect' => 'https://my-app.dev',
],
*Note: if committing config/services.php to a public repo, extract these values to your .env file and reference them via the env helper method;
In config/app.php add the SocialiteProviders/Generators service provider to the providers array:
'providers' => [
...
/*
* Package Service Providers...
*/
Laravel\Socialite\SocialiteServiceProvider::class,
// This is a dependency of the socialiteproviders/microsoft-graph provider, and will be installed with the provider via it's composer.json file
SocialiteProviders\Manager\ServiceProvider::class,
Register the Socialize facade (also in config/app.php):
'aliases' => [
...
'Socialize' => 'Laravel\Socialite\Facades\Socialite',
],
Register an event listener in app/Providers/EventServiceProvider.php:
protected $listen = [
...
'SocialiteProviders\Manager\SocialiteWasCalled' => [
'SocialiteProviders\Graph\GraphExtendSocialite#handle'
],
];
Create your controller to handle the requests:
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Socialize;
class AuthController extends \App\Http\Controllers\Controller
{
/**
* Redirect the user to the Graph authentication page.
*
* #return Response
*/
public function redirectToProvider()
{
return Socialize::with('graph')->redirect();
}
/**
* Obtain the user information from graph.
*
* #return Response
*/
public function handleProviderCallback(Request $request)
{
$user = Socialize::with('graph')->user();
// $user->token;
}
}
Finally add your routes in routes/web.php:
<?php
Route::get('auth/graph', 'Auth\AuthController#redirectToProvider');
Route::get('auth/graph/callback','Auth\AuthController#handleProviderCallback');
If anyone still arrives here with the same error, but using the SocialiteProviders Microsoft provider already:
Check if you have set up the library correctly.
Make sure to install socialiteproviders/microsoft from composer
Add the SocialiteProviders Manager to your config/providers.php: \SocialiteProviders\Manager\ServiceProvider::class
Add the event listener to your app/Providers/EventServiceProvider.php:
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
[SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class, 'handle'],
],
The last step is important, and what caused the error for me, because I didn't understand the event listener is required (and not just an optional way to extend the provider).
So this might seem obvious but both in my case and judging from the info provided in the question, this step is also missing. I changed from the current Microsoft to the Graph and still got the same error, however I then realized this error happens when the Driver is not registered in the service provider. Make sure you are using the same spelling of the service provider in vendor and that you include the Service provider, in my case:
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* #var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
// ... other providers
/*--- I forgot this --->*/\SocialiteProviders\Graph\GraphExtendSocialite::class.'#handle',
],
];
/**
* Register any events for your application.
*
* #return void
*/
public function boot()
{
//
}
/**
* Determine if events and listeners should be automatically discovered.
*
* #return bool
*/
public function shouldDiscoverEvents()
{
return false;
}
}
This added worked with microsoft graph, driver: "graph" from: https://github.com/SocialiteProviders/Microsoft-Graph
I never got to try with the listed driver "Microsoft" on socialiteproviders.com
and as of the time of this writing Graph was removed from that website, however all I care is that it works and it worked as expected!
I want to extend the Laravel stock authentication to use an OAuth server for user retrieval and authentication while taking advantage of the existing functionality. I already managed to extend the EloquentUserProvider to partially overwrite/extend the implementations of the Illuminate\Contracts\Auth\UserProvider contract. The current implementation looks like this:
class EloquentOauthServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return HcOAuthProvider;
*/
public function boot()
{
Auth::provider('oauth',function($app){
$model = $app['config']['auth.providers.oauth.model'];
$repository = new OauthUserRepository();
return new EloquentOauthUserProvider($app['hash'], $model, $repository);
});
}
}
In the auth.php config I changed the descriptors like this:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'oauth',
],
]
'providers' => [
'oauth' => [
'driver' => 'oauth',
'model' => App\Models\User::class,
],
]
This is working so far, I can overwrite methods to add my logic. But I realized that I also need to overwrite some methods of the SessionGuard class (login and logout to be specific) since I want to save and retrieve OAuth specific tokens using the Laravel session implementation. There are a couple of suggestions around but they are either not working (Maybe they worked before Laravel 5.2) or would require to overwrite the Authmanager which looks like overkill.
So my question is: What I have to do in Laravel 5.2 to overwrite the SessionGuard?
Recently I've had the same problem, so maybe the solution is something like this.
<?php
namespace App\CoreExtensions;
use Illuminate\Auth\SessionGuard;
use Illuminate\Contracts\Auth\Authenticatable;
class SessionGuardExtended extends SessionGuard
{
/**
* Log a user into the application.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param bool $remember
* #return void
*/
public function login(Authenticatable $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier());
if ($remember) {
$this->refreshRememberToken($user);
$this->queueRecallerCookie($user);
}
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
}
In AppServiceProvider.php
public function boot()
{
Auth::extend(
'sessionExtended',
function ($app) {
$provider = new EloquentUserProvider($app['hash'], config('auth.providers.users.model'));
return new SessionGuardExtended('sessionExtended', $provider, app()->make('session.store'), request());
}
);
}
In Config/auth.php driver should be renamed
'web' => [
'driver' => 'sessionExtended',
'provider' => 'users',
],
Extend guard:
use Illuminate\Auth\SessionGuard;
class MySessionGuard extends SessionGuard {
// ...
}
Update configs
'web' => [
'driver' => 'my-session',
'provider' => 'users',
],
Bind new guard in AuthServiceProvider.php:
public function boot()
{
Auth::extend(
'my-session',
function ($app, $name, array $config) {
/* Copy functionality form Auth::createSessionDriver */
$guard = new MySessionGuard($name, Auth::createUserProvider($config['provider'] ?? null), $this->app['session.store']);
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($this->app['cookie']);
}
if (method_exists($guard, 'setDispatcher')) {
$guard->setDispatcher($this->app['events']);
}
if (method_exists($guard, 'setRequest')) {
$guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
}
return $guard;
}
);
// ...
}