Search code examples
phplaravelmulti-tenantlaravel-passport

Issue using Laravel Passport with Tenancy for Laravel


I am creating a subdomain-based multi-tenant laravel project using Tenancy For Laravel, everything is working fine except when trying to authenticate my API requests using Passport. How it works now is that I have a main database (multi-tenants) in which I declare tenants and specify the respective subdomain, and then each tenant has its own database (tenantfoo). I get through the Auth::check and credentials are checked in the correct database but when I try to create the token it stops using the tenant database (tenantfoo) and tries to create the token in the main database (multi-tenant) and gives the following exception.

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'multi-tenant.oauth_personal_access_clients' doesn't exist
(SQL: select exists(select * from `oauth_personal_access_clients`) as `exists`)

provided that if I do try to get the users using User::all(); I get the correct users belonging the the respective tenant.

I tried following the Integration with Passport from their docs but it didn't work out for me..

So what am asking, is there a way to manually create my token?

I tried doing this but still gives the same error

public function loginManual() {
 if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
            $token = Auth::user()->createToken('Laravel Password Grant Client')->accessToken;
                $response = ['token' => $token];
                return $response
        } else {
            $response = ["message" => "Password mismatch"];
            return $response
        }
}

Attached below is some of the code, feel free to ask for anything not provided.

AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
use Illuminate\Support\Facades\Route;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Passport::ignoreMigrations();
        Passport::routes(null, ['middleware' => [
            // You can make this simpler by creating a tenancy route group
            InitializeTenancyByDomain::class,
            PreventAccessFromCentralDomains::class,
        ]]);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Passport::loadKeysFrom(base_path(config('passport.key_path')));
        Schema::defaultStringLength(191);
    }
}

AuthServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;

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

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

        Passport::routes();

    }
}

Solution

  • So I ended up creating a middleware and manually changing the database on the fly to the correct one. And added the middleware to the Passport::routes(); initiation. This way all the passport routes will be checked against the correct database.

    Middleware Class:

    class ChangeDatabaseConnection
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle(Request $request, Closure $next)
        {
            if ($request->tenant != 'www' && $request->tenant != NULL) {
                $db_name = 'tenant'. $request->tenant;
                \DB::disconnect(); 
                \Config::set('database.connections.mysql.database', $db_name); 
                \DB::reconnect();
                $db = \Config::get('database.connections.mysql.database');
                return $next($request);
            } else {
                $url = $request->url();
                $parsedUrl = parse_url($url);
                $host = explode('.', $parsedUrl['host']);
                $subdomain = $host[0];
                if ($subdomain != NULL && $subdomain != 'www') {
                    $db_name = 'tenant'. $subdomain;
                    \DB::disconnect(); 
                    \Config::set('database.connections.mysql.database', $db_name); 
                    \DB::reconnect();
                    $db = \Config::get('database.connections.mysql.database');
                    return $next($request);
                }
            }
            
            return $next($request);
        }
    }
    

    Passport Routes Declaration (AuthServiceProvider):

     Passport::routes(null, ['middleware' => ['pre-db']]);
    

    I only change the database when a tenant is provided (not 'www') otherwise the main database (declared in .env) is used. This is working fine for me, if I face any issues I'll update my answer with how I solve them.