Tenant Context Management in Filament with Livewire Updates

Introduction

In multi-tenant applications built with Filament, ensuring the correct tenant context is crucial for data isolation and security. A recent fix in the platform addresses an issue where Livewire update requests bypassed Filament's tenant middleware, potentially leading to incorrect schema resolution for super admin users. This post explores the problem, the solution, and the implications for maintaining tenant context.

The Problem: Bypassing Tenant Middleware

Filament typically relies on middleware to establish the tenant context for each request. This middleware ensures that the application operates within the correct database schema or connection associated with the current tenant. However, Livewire update requests, which handle dynamic interactions on a page, were found to bypass this middleware. This meant that the SetTenantSchemaMiddleware was not executed, potentially causing issues for super admin users accessing different tenants.

Specifically, super admins whose company_id differed from the viewed tenant could experience ModelNotFoundException errors (404) on edit/view pages because the system couldn't resolve the correct schema.

The Solution: Session-Based Tenant Storage

To address this issue, the solution involves storing the tenant ID in the session during the initial page load. This provides a fallback mechanism for resolving the tenant context when the middleware is bypassed. Tenant resource classes now check the session for the tenant ID before falling back to the user's company_id.

Here's a simplified example of how tenant context might be resolved:

<?php

namespace App\Services;

use Illuminate\Support\Facades\Session;

class TenantContext
{
    public static function resolveTenantId(): ?string
    {
        // Check if tenant ID is stored in session
        if (Session::has('tenant_id')) {
            return Session::get('tenant_id');
        }

        // Fallback to user's company ID (if applicable)
        if (auth()->check() && auth()->user()->company_id) {
            return auth()->user()->company_id;
        }

        return null;
    }
}

This code snippet illustrates how the resolveTenantId function first checks the session for a stored tenant_id. If found, it returns that value. Otherwise, it falls back to the authenticated user's company_id, if available. This ensures that even when the middleware is bypassed, the application can still determine the correct tenant context.

Implications and Best Practices

This fix highlights the importance of ensuring that all requests, including Livewire updates, properly establish the tenant context in multi-tenant applications. Here are a few best practices to consider:

  1. Centralized Tenant Resolution: Use a consistent and centralized approach for resolving the tenant context, as demonstrated in the example above.
  2. Middleware Coverage: Ensure that all relevant routes and request types are covered by tenant middleware.
  3. Fallback Mechanisms: Implement fallback mechanisms, such as session-based storage, to handle cases where middleware might be bypassed.

Conclusion

Maintaining accurate tenant context is paramount in multi-tenant applications. By storing the tenant ID in the session and providing a fallback mechanism, the platform ensures that Livewire update requests correctly resolve the tenant context, preventing data access issues and maintaining data isolation. When building multi-tenant applications, double-check that your tenant resolution strategy covers all request types and edge cases to avoid similar issues. Consider adding a test suite that specifically targets tenant isolation in various scenarios.

Tenant Context Management in Filament with Livewire Updates
GERARDO RUIZ

GERARDO RUIZ

Author

Share: