iprote/saas-auth

Multi-tenant role and permission management for Laravel SaaS applications

v1.0.2 2025-06-12 08:25 UTC

This package is auto-updated.

Last update: 2025-06-12 08:34:53 UTC


README

A Laravel package for multi-tenant role and permission management, perfect for SaaS applications where users can belong to multiple tenants with different roles in each.

Features

  • 🏢 Multi-tenant architecture - Users can belong to multiple tenants
  • 🎭 Tenant-scoped roles - Different roles per tenant for the same user
  • 🔐 Permission management - Fine-grained permissions within each tenant
  • 🛡️ Middleware protection - Route-level tenant permission/role checking
  • 🎨 Blade directives - Template-level authorization checks
  • Easy installation - One command setup
  • 🔧 Configurable - Customizable table names and user models
  • ⚛️ Inertia.js + React support - Modern frontend integration

Installation

Install the package via Composer:

composer require iprote/saas-auth

Publish and run the migrations:

php artisan saas-auth:install
php artisan migrate

Add the HasTenantRoles trait to your User model:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Iprote\SaasAuth\Traits\HasTenantRoles;

class User extends Authenticatable
{
    use HasTenantRoles;
    
    // ... rest of your User model
}

Basic Usage

Creating Tenants, Roles, and Permissions

use Iprote\SaasAuth\Models\Tenant;
use Iprote\SaasAuth\Models\Role;
use Iprote\SaasAuth\Models\Permission;

// Create a tenant
$tenant = Tenant::create([
    'name' => 'Acme Corporation',
    'slug' => 'acme-corp'
]);

// Create permissions
$editPermission = Permission::create(['name' => 'edit-posts']);
$deletePermission = Permission::create(['name' => 'delete-posts']);

// Create a role for the tenant
$editorRole = Role::createForTenant('editor', $tenant);

// Assign permissions to the role
$editorRole->givePermissionTo($editPermission);
$editorRole->givePermissionTo($deletePermission);

Assigning Users to Tenants

// Assign user to tenant with a role
$user->assignRoleToTenant($editorRole, $tenant);

// Or using the facade
use Iprote\SaasAuth\Facades\SaasAuth;

SaasAuth::assignUserToTenant($user, $tenant, $editorRole);

Checking Permissions and Roles

// Check if user has permission in tenant
if ($user->hasPermissionInTenant('edit-posts', $tenant)) {
    // User can edit posts in this tenant
}

// Check if user has role in tenant
if ($user->hasRoleInTenant('editor', $tenant)) {
    // User is an editor in this tenant
}

// Get user's role in tenant
$role = $user->getRoleInTenant($tenant);

// Get user's permissions in tenant
$permissions = $user->getPermissionsInTenant($tenant);

Middleware

Protect your routes with tenant-aware middleware:

// Require specific permission in tenant
Route::middleware(['auth', 'tenant_can:edit-posts,tenant'])->group(function () {
    Route::put('/tenants/{tenant}/posts/{post}', [PostController::class, 'update']);
});

// Require specific role in tenant
Route::middleware(['auth', 'tenant_role:editor,tenant'])->group(function () {
    Route::get('/tenants/{tenant}/dashboard', [DashboardController::class, 'index']);
});

The middleware expects the tenant to be passed as a route parameter. You can customize the parameter name:

// Use 'organization' instead of 'tenant'
Route::middleware(['tenant_can:edit-posts,organization'])->group(function () {
    Route::put('/orgs/{organization}/posts/{post}', [PostController::class, 'update']);
});

Blade Directives

Use Blade directives for template-level authorization:

@tenantCan('edit-posts', $tenant)
    <a href="{{ route('posts.edit', [$tenant, $post]) }}">Edit Post</a>
@endTenantCan

@tenantRole('admin', $tenant)
    <a href="{{ route('tenant.settings', $tenant) }}">Tenant Settings</a>
@endTenantRole

Inertia.js + React Support

For modern frontend applications using Inertia.js with React, the package provides seamless integration with React components and hooks. See the Inertia React Guide for detailed setup and usage examples.

// React Permission Guard Component
<PermissionGuard permission="edit-posts">
    <button onClick={handleEdit}>Edit Post</button>
</PermissionGuard>

// React Permission Hook
const { hasPermission, hasRole } = usePermissions();

if (hasPermission('edit-posts')) {
    // User can edit posts
}

API Reference

User Methods (via HasTenantRoles trait)

// Tenant relationships
$user->tenants; // Get all tenants for user
$user->belongsToTenant($tenant); // Check if user belongs to tenant

// Role management
$user->assignRoleToTenant($role, $tenant);
$user->removeRoleFromTenant($role, $tenant);
$user->removeFromTenant($tenant);

// Role checking
$user->hasRoleInTenant($roleName, $tenant);
$user->hasAnyRoleInTenant(['admin', 'editor'], $tenant);
$user->hasAllRolesInTenant(['editor', 'publisher'], $tenant);
$user->getRoleInTenant($tenant);

// Permission checking
$user->hasPermissionInTenant($permissionName, $tenant);
$user->hasAnyPermissionInTenant(['edit', 'delete'], $tenant);
$user->hasAllPermissionsInTenant(['read', 'write'], $tenant);
$user->getPermissionsInTenant($tenant);

// Utility methods
$user->getAllTenantRoles(); // Get roles across all tenants
$user->getTenantsWithRole($roleName);
$user->getTenantsWithPermission($permissionName);

Tenant Methods

$tenant->users; // Get all users in tenant
$tenant->roles; // Get all roles for tenant

$tenant->addUser($user, $role); // Add user with optional role
$tenant->removeUser($user); // Remove user from tenant
$tenant->hasUser($user); // Check if user belongs to tenant
$tenant->getUserRole($user); // Get user's role in tenant

Role Methods

$role->tenant; // Get tenant that owns the role
$role->permissions; // Get all permissions for role

// Scopes
Role::forTenant($tenant)->get(); // Get roles for specific tenant
Role::global()->get(); // Get global roles (no tenant)

// Permission management
$role->givePermissionTo($permission);
$role->revokePermissionTo($permission);
$role->hasPermissionTo($permission);
$role->hasPermission($permissionName);
$role->syncPermissions($permissions);

// Factory method
Role::createForTenant($name, $tenant, $attributes);

Permission Methods

$permission->roles; // Get all roles that have this permission

// Factory methods
Permission::findByName($name, $guardName);
Permission::findOrCreate($name, $guardName);

// Scopes
Permission::forTenant($tenant)->get(); // Get permissions used in tenant

Facade Usage

use Iprote\SaasAuth\Facades\SaasAuth;

// Create entities
$tenant = SaasAuth::createTenant('Company Name');
$role = SaasAuth::createRole('manager', $tenant);
$permission = SaasAuth::createPermission('manage-users');

// User management
SaasAuth::assignUserToTenant($user, $tenant, $role);
SaasAuth::userHasPermissionInTenant($user, 'manage-users', $tenant);
SaasAuth::userHasRoleInTenant($user, 'manager', $tenant);

// Utility methods
$tenants = SaasAuth::getUserTenants($user);
$role = SaasAuth::getUserRoleInTenant($user, $tenant);

Configuration

Publish the config file to customize the package:

php artisan vendor:publish --tag=saas-auth-config

The config file allows you to customize:

  • User model class
  • Table names
  • Column names
  • Cache settings
  • Middleware aliases
// config/saas-auth.php
return [
    'user_model' => App\Models\User::class,
    
    'table_names' => [
        'tenants' => 'tenants',
        'roles' => 'roles',
        'permissions' => 'permissions',
        'role_has_permissions' => 'role_has_permissions',
        'tenant_user' => 'tenant_user',
    ],
    
    // ... other configuration options
];

Database Schema

The package creates the following tables:

  • tenants - Stores tenant information
  • permissions - Stores available permissions
  • roles - Stores roles (with optional tenant_id for scoping)
  • role_has_permissions - Pivot table for role-permission relationships
  • tenant_user - Pivot table for user-tenant relationships with role assignment

Advanced Usage

Global vs Tenant Roles

You can create global roles (not tied to any tenant) by omitting the tenant:

// Global role (works across all tenants)
$globalAdmin = Role::create([
    'name' => 'super-admin',
    'tenant_id' => null
]);

// Tenant-specific role
$tenantAdmin = Role::createForTenant('admin', $tenant);

Custom User Model

If you're using a custom User model, update the config:

// config/saas-auth.php
'user_model' => App\Models\CustomUser::class,

Route Model Binding

The middleware works seamlessly with route model binding:

// routes/web.php
Route::middleware(['tenant_can:edit-posts'])->group(function () {
    Route::put('/tenants/{tenant}/posts/{post}', [PostController::class, 'update']);
});

// In your controller
public function update(Tenant $tenant, Post $post)
{
    // The middleware has already verified the user has 'edit-posts' permission in this tenant
    // Your update logic here
}

Frontend Integration

Traditional Blade Templates

Use the provided Blade directives for server-side rendering:

@tenantCan('edit-posts', $tenant)
    <a href="{{ route('posts.edit', [$tenant, $post]) }}">Edit Post</a>
@endTenantCan

Inertia.js + React

For modern SPAs, use the React components and hooks:

import PermissionGuard from '@/Components/PermissionGuard';
import usePermissions from '@/Hooks/usePermissions';

function ProjectCard({ project, tenant }) {
    const { hasPermission } = usePermissions();
    
    return (
        <div>
            <h3>{project.name}</h3>
            <PermissionGuard permission="edit-projects">
                <button>Edit</button>
            </PermissionGuard>
        </div>
    );
}

See the complete Inertia React Guide for detailed examples.

Documentation

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Security

If you discover any security related issues, please email the maintainers instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.

Credits

Support

If you find this package useful, please consider starring the repository. For support questions, please use the GitHub issues.