0

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,

  1. 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."}

  1. 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.

2 Answers 2

1

Try to add

\App\Http\Middleware\EnforceTenancy::class

into the beginning of $middlewarePriority array in Kernel.php

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.