Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

james1232's avatar

Multi Tenancy & Admin Area

Hi,

I've been working on a multi-tenancy project and I understand the basic concept of multi-tenancy. However, I'm struggling with how to integrate an admin area within the same project. The best solution I've come across so far is to treat the admin area as a tenant and add a qualifying column called is_admin to the tenants table.

While this approach works, it seems insecure and somewhat clunky. Is there a better way to handle the admin area in a multi-tenancy application? Specifically, how can I securely and efficiently manage both tenant-specific data and global admin functionalities within the same project?

0 likes
8 replies
LaryAI's avatar
Level 58

To integrate an admin area within a multi-tenancy project, you can follow a more structured and secure approach by separating the concerns of tenant-specific data and global admin functionalities. Here’s a solution that leverages Laravel's middleware, guards, and route groups to achieve this:

Step 1: Define Middleware for Tenant and Admin

Create middleware to differentiate between tenant and admin access. You can use Laravel's artisan command to create middleware:

php artisan make:middleware TenantMiddleware
php artisan make:middleware AdminMiddleware

In TenantMiddleware, you can check if the user belongs to a tenant:

// app/Http/Middleware/TenantMiddleware.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class TenantMiddleware
{
    public function handle($request, Closure $next)
    {
        if (Auth::check() && Auth::user()->is_tenant) {
            return $next($request);
        }

        return redirect('/login');
    }
}

In AdminMiddleware, you can check if the user is an admin:

// app/Http/Middleware/AdminMiddleware.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class AdminMiddleware
{
    public function handle($request, Closure $next)
    {
        if (Auth::check() && Auth::user()->is_admin) {
            return $next($request);
        }

        return redirect('/login');
    }
}

Step 2: Register Middleware

Register the middleware in app/Http/Kernel.php:

protected $routeMiddleware = [
    // ...
    'tenant' => \App\Http\Middleware\TenantMiddleware::class,
    'admin' => \App\Http\Middleware\AdminMiddleware::class,
];

Step 3: Define Routes for Tenant and Admin

Use route groups to separate tenant and admin routes:

// routes/web.php

// Tenant routes
Route::middleware(['auth', 'tenant'])->group(function () {
    Route::get('/tenant/dashboard', [TenantController::class, 'dashboard'])->name('tenant.dashboard');
    // Add more tenant-specific routes here
});

// Admin routes
Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard');
    // Add more admin-specific routes here
});

Step 4: Create Guards for Tenant and Admin (Optional)

If you need more control over authentication, you can create custom guards in config/auth.php:

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

    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],

    'tenant' => [
        'driver' => 'session',
        'provider' => 'tenants',
    ],
],

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

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

    'tenants' => [
        'driver' => 'eloquent',
        'model' => App\Models\Tenant::class,
    ],
],

Step 5: Implement Controllers

Create controllers for tenant and admin functionalities:

php artisan make:controller TenantController
php artisan make:controller AdminController

In TenantController:

// app/Http/Controllers/TenantController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TenantController extends Controller
{
    public function dashboard()
    {
        return view('tenant.dashboard');
    }
}

In AdminController:

// app/Http/Controllers/AdminController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminController extends Controller
{
    public function dashboard()
    {
        return view('admin.dashboard');
    }
}

Step 6: Create Views

Create views for tenant and admin dashboards:

  • resources/views/tenant/dashboard.blade.php
  • resources/views/admin/dashboard.blade.php

Conclusion

By using middleware, route groups, and optionally custom guards, you can securely and efficiently manage both tenant-specific data and global admin functionalities within the same project. This approach keeps the concerns separated and ensures that only authorized users can access the respective areas.

Snapey's avatar

using single database or database per tenant?

james1232's avatar

@Snapey Currently single database but it might need to be changed to database per tenant in the future due to client Information separation requirements (legal stuff).

So an approach to either one or both would be great!

Any information with useful resource links is also more then welcome.

martinbean's avatar

@james1232 If you’re using a single database then I’m not sure what the problem is? You’d just query your records without any tenant filters.

If you do move to the database-per-tenant approach, then you’re not going to be able to create an admin panel that shows data from all tenants.

james1232's avatar

@martinbean I'm having trouble in understanding that to be the case. Every tenancy package I've come across always depends on the tenant_id record not being empty.

james1232's avatar

@Snapey I'm struggling to understand how an application can handle both tenant functionality and admin functionality, such as for a group or team of employees. It seems like these should be separate applications that communicate with each other. From what I understand, multi-tenancy means creating separate instances of an application for each tenant. In contrast, the admin panel is meant for users who aren't associated with any specific tenant and have distinct functions. Additionally, many of these packages seem to require every user to be part of a tenant, which adds to my confusion about how these different functionalities can coexist within the same application, given their fundamentally different roles.

martinbean's avatar

@james1232 I didn’t say make the tenant_id column be empty? I said you’d just query records without doing any filtering on the tenant ID column, to get records from all tenants:

// Get latest posts for a single tenant
$posts = $tenant->posts()->latest()->get();
// Get latest posts from all tenants
$posts = Post::query()->latest()->get();

Please or to participate in this conversation.