How to implement granular sub-user permissions, enforce tenant-wide subscription lockouts, and turn team collaboration into a paid SaaS add-on.
In B2B SaaS platforms (like ERP or billing systems), workspace owners frequently invite sub-users (referred to as Collaborators) to manage day-to-day operations. However, delegating access presents two critical engineering challenges:
1. Granular Permission Scoping: Collaborators must not have blanket admin privileges. A warehouse manager might create inventory items but must be restricted from deleting invoices or editing tax settings.
2. Cascading Tenant Lockouts: When a primary account's subscription expires, document creation (sales, expenses, invoices) must be instantly blocked across all linked collaborators, preventing sub-users from bypassing billing expiration checks.
Here is how we designed a secure Role-Based Access Control (RBAC) pipeline and turned the "Collaborator Management" module into a revenue-generating subscription add-on.
Instead of assigning rigid global roles (admin, staff), create a granular permission schema mapping specific actions (invoices.create, invoices.delete, expenses.view) to custom roles defined by the account owner.
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Services/PermissionService.php
namespace App\Services;
use App\Models\User;
class PermissionService
{
public static function canUserPerform(User $user, string $permission): bool
{
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 1. Primary workspace owners bypass granular checks
if ($user->is_workspace_owner) {
return true;
}
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// 2. Fetch collaborator role permissions
$permissions = $user->role ? $user->role->permissions->pluck('name')->toArray() : [];
return in_array($permission, $permissions);
}
}You can then enforce these rules cleanly inside Laravel Middleware or Policy definitions:
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Http/Middleware/CheckModulePermission.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use App\Services\PermissionService;
class CheckModulePermission
{
public function handle(Request $request, Closure $next, string $permission)
{
$user = $request->user();
if (!PermissionService::canUserPerform($user, $permission)) {
return response()->json([
'error' => 'You do not have permission to execute this action.'
], 403);
}
return $next($request);
}
}A common security vulnerability occurs when subscription expiration checks only target the currently logged-in user. If a sub-user logs in, their individual account status might appear active, allowing them to create sales and expense documents even after the workspace owner's subscription has expired.
To fix this, resolve the primary tenant's subscription status during every document creation request:
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Http/Middleware/EnsureActiveSubscription.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureActiveSubscription
{
public function handle(Request $request, Closure $next)
{
$user = $request->user();
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Resolve the workspace owner (primary tenant)
$owner = $user->is_workspace_owner ? $user : $user->workspaceOwner;
if (!$owner || !$owner->hasActiveSubscription()) {
return response()->json([
'error' => 'Subscription expired. Please ask your account administrator to extend your plan.',
'code' => 'SUBSCRIPTION_EXPIRED'
], 402);
}
return $next($request);
}
}After building a reliable RBAC system and securing sub-user access controls, the team recognized that multi-user collaboration was a key differentiator for growing businesses.
Instead of bundling collaborator management into all base plans, we gated the feature behind a paid subscription add-on:
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Models/Workspace.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Workspace extends Model
{
public function canAddCollaborators(): bool
{
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Check if tenant has purchased the Collaborators Add-On or has Enterprise Tier
return $this->hasAddon('collaborators_module') || $this->plan_tier === 'enterprise';
}
public function maxCollaboratorLimit(): int
{
if (!$this->canAddCollaborators()) {
return 0; class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Free / Standard plan without add-on
}
return $this->addon_seats ?? 5;
}
}By combining granular permission scoping, tenant-wide subscription enforcement, and feature gating, we turned an internal user management feature into a secure, revenue-generating SaaS module.