A complete, production-ready authorization platform for Laravel — combining RBAC, ABAC, Role Hierarchy, Teams, Audit Logging, and 30+ enterprise features in a single package. 141 tests. Zero breaking changes. Works with Laravel 10–13.
🔗 GitHub Repository · 📦 Packagist
📋 Table of Contents
- Introduction
- Quick Start (5 Minutes)
- Core Authorization Features
- Enterprise Features
- Laravel Integration
- Developer Experience
- Architecture Overview
- Real-World Examples
- Testing & Quality
- Installation & Configuration
- Comparison with Spatie
🎯 Introduction
Laravel Permission Manager is the most advanced, enterprise-grade authorization engine available for Laravel applications. It goes far beyond basic role-based access control, delivering a complete authorization platform that combines:
- 🎭 RBAC (Role-Based Access Control)
- 🧠 ABAC (Attribute-Based Access Control)
- 🌳 Role Hierarchy with multi-level inheritance
- 🏢 Multi-Tenancy / Teams
- 📝 Audit Logging
- ⏱️ Temporary Permissions
- 🔒 Explicit Allow/Deny
- 🛡️ Advanced Middleware DSL
All in a single, cohesive, backward-compatible package with 141 passing tests and 226 assertions.
Whether you're building a simple blog or a complex SaaS platform with multiple tenants, conditional permissions, and strict audit requirements — this package handles it all.
composer require hosseinhezami/laravel-permission-manager
⚡ Quick Start (5 Minutes)
Step 1: Install & Publish
composer require hosseinhezami/laravel-permission-manager
php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="migrations"
php artisan migrate
Step 2: Add Trait to User Model
use HosseinHezami\PermissionManager\Traits\PermissionTrait;
class User extends Authenticatable
{
use PermissionTrait;
}
Step 3: Create Roles & Assign Permissions
php artisan role:create admin "Administrator" "Full system access"
php artisan permission:create "users.view"
php artisan permission:create "users.edit"
php artisan role:assign-permission admin "users.view,users.edit"
Step 4: Assign Role & Check Access
$user->assignRole('admin');
$user->hasPermissionTo('users.view'); // true
That's it. A fully functional RBAC system in under 5 minutes. But this is just the surface...
🎯 Core Authorization Features
1. Role-Based Access Control (RBAC)
The foundation of the system. Multiple roles per user, permissions assigned to roles.
$user->assignRole('admin');
$user->assignRole(['editor', 'manager']);
$user->revokeRole('editor');
$user->hasRole('admin'); // true
$user->hasAnyRole(['admin', 'super-admin']); // true
$user->hasAllRoles(['admin', 'manager']); // true
$user->lacksRole('super-admin'); // true
2. Direct User Permissions
Grant permissions directly to a user without needing a role. Perfect for exceptions, one-off access, or special cases.
// Grant direct permission
$user->givePermissionTo('reports.export');
// Check it
$user->hasDirectPermission('reports.export'); // true
// Deny directly
$user->denyPermissionTo('users.delete');
// Revoke
$user->revokePermissionTo('reports.export');
3. Explicit Allow / Deny System
The deny permission has higher priority than allow. This enables powerful exception-based access control.
// Role grants all posts.* permissions
$editor->assignPermission('posts.*');
// But this specific user cannot delete
$user->denyPermissionTo('posts.delete');
// Result:
$user->hasPermissionTo('posts.edit'); // ✅ true (from role wildcard)
$user->hasPermissionTo('posts.delete'); // ❌ false (explicit deny wins)
Resolution Order (highest → lowest priority):
1. Super Admin bypass
2. Explicit User DENY ← highest
3. Explicit Role DENY
4. Explicit User ALLOW
5. Role ALLOW
6. Inherited Role permissions
7. Wildcard matching
8. Default: DENY ← lowest
4. Wildcard Permissions
Flexible pattern matching with negation support:
$role->assignPermission('users.*'); // All user operations
$role->assignPermission('*.edit'); // All edit operations
$role->assignPermission('admin.*'); // All admin operations
$role->assignPermission('!users.delete'); // Everything EXCEPT users.delete
| Pattern | Matches | Doesn't Match |
|---|---|---|
users.* |
users.edit, users.delete
|
posts.edit |
*.edit |
users.edit, posts.edit
|
users.view |
* |
Everything | Nothing |
!users.delete |
Everything except users.delete
|
users.delete |
5. Role Hierarchy (Multi-Level Inheritance)
Roles can inherit from other roles, creating powerful permission chains:
// Build hierarchy: super-admin → admin → editor → viewer
$superAdmin->inheritFrom('admin');
$admin->inheritFrom('editor');
$editor->inheritFrom('viewer');
// Assign specific permissions at each level
$viewer->assignPermission('posts.view');
$editor->assignPermission('posts.edit');
$admin->assignPermission('posts.delete');
$superAdmin->assignPermission('system.config');
// A user with super-admin gets ALL permissions through the chain
$user->assignRole('super-admin');
$user->hasPermissionTo('posts.view'); // ✅ from viewer (3 levels up)
$user->hasPermissionTo('posts.edit'); // ✅ from editor (2 levels up)
$user->hasPermissionTo('posts.delete'); // ✅ from admin (1 level up)
$user->hasPermissionTo('system.config'); // ✅ from super-admin (direct)
Cycle Detection
The system automatically prevents circular inheritance:
$roleA->inheritFrom('roleB');
$roleB->inheritFrom('roleA');
// ❌ Throws CyclicRoleInheritanceException
6. Temporary / Expiring Permissions
Grant time-limited access that automatically expires:
// 24-hour access
$user->givePermissionTo('reports.export', 'allow', now()->addDay());
// 30-day contractor access
$user->givePermissionTo('projects.access', 'allow', now()->addDays(30));
// After expiration → automatically returns false
// No cron job needed for permission checks!
// Clean up expired records weekly
php artisan permission:prune --days=7
7. Super Admin Bypass
Configurable root access that skips all permission checks:
// config/permission-manager.php
'super_admin' => [
'enabled' => true,
'role_slug' => 'super-admin',
'bypass_all' => true,
],
// Usage
$user->isSuperAdmin(); // true
$user->hasPermissionTo('anything.xyz.123'); // true (bypasses everything)
🏢 Enterprise Features
8. Teams / Multi-Tenancy
Essential for SaaS applications where users belong to multiple organizations:
// Create teams
$engineering = Team::createTeam(['name' => 'Engineering', 'slug' => 'engineering']);
$marketing = Team::createTeam(['name' => 'Marketing', 'slug' => 'marketing']);
// User joins both teams
$user->joinTeam($engineering);
$user->joinTeam($marketing);
// Different roles per team
$user->assignRoleForTeam('admin', $engineering);
$user->assignRoleForTeam('editor', $marketing);
// Check team-specific roles
$user->hasRoleForTeam('admin', $engineering); // true
$user->hasRoleForTeam('admin', $marketing); // false
$user->hasRoleForTeam('editor', $marketing); // true
// Leave team
$user->leaveTeam($marketing);
Team Context in Requests
// Via middleware
Route::middleware(['pm.team:header,X-Team-Id'])->group(function () {
// All permissions scoped to team from X-Team-Id header
});
// Programmatically
PermissionManager::setTeam($currentTeam);
$user->hasPermissionTo('projects.view'); // Scoped to current team
PermissionManager::clearTeam();
9. ABAC (Attribute-Based Access Control)
The most powerful feature. Define conditions that must be met for a permission to grant access:
// Only allow editing if user owns the post AND it's a draft
PermissionCondition::create([
'permission_id' => $permission->id,
'name' => 'owner-and-draft',
'conditions' => [
'all' => [
['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
['field' => 'resource.status', 'operator' => '=', 'value' => 'draft'],
],
],
]);
// Usage
$user->canPermission('posts.update', $post);
// Returns true ONLY if $user->id === $post->owner_id AND $post->status === 'draft'
Supported Operators
| Operator | Description | Example |
|---|---|---|
= ==
|
Equal | user.id = resource.owner_id |
!= !==
|
Not equal | user.role != "banned" |
> >=
|
Greater than | user.level >= resource.required_level |
< <=
|
Less than | user.failed_attempts < 3 |
in |
In array | resource.status in ["draft", "pending"] |
not_in |
Not in array | user.id not_in [1, 2, 3] |
contains |
String contains | user.email contains "@company.com" |
starts_with |
String starts with | resource.path starts_with "admin/" |
exists |
Is not null | resource.published_at exists |
not_exists |
Is null | resource.deleted_at not_exists |
Logical Operators & Nesting
// AND (all must match)
['all' => [...conditions]]
// OR (any must match)
['any' => [...conditions]]
// NOT (negate)
['not' => ...condition]
// Complex nesting
['all' => [
['field' => 'resource.status', 'operator' => '!=', 'value' => 'archived'],
['any' => [
['field' => 'user.id', 'operator' => '=', 'value' => 'resource.owner_id'],
['field' => 'user.role', 'operator' => '=', 'value' => 'admin'],
]],
]]
🔒 Security: The condition engine uses a whitelist-based evaluator. No
eval(), no code execution. Only safe, predefined operators.
10. Audit Logging
Track every permission change with full context:
// Automatically logged when you:
$user->assignRole('admin');
$role->assignPermission('users.delete');
$user->givePermissionTo('reports.export');
// Query the audit trail
PermissionAudit::byActor($adminId)->get();
PermissionAudit::action('granted')->latest()->limit(50)->get();
// Each record includes:
// - actor_id (who made the change)
// - action (granted, revoked, created, etc.)
// - subject_type (user, role, permission)
// - ip_address, user_agent
// - metadata (JSON)
11. Authorization Audit Trail
Optionally log every permission check (not just mutations):
// config/permission-manager.php
'authorization_logging' => [
'enabled' => true,
'denied_only' => true, // Only log failed attempts
'sample_rate' => 0.1, // Log 10% (for high-traffic apps)
],
12. Multi-Guard Support
True isolation between authentication guards:
// Create guard-specific roles
Role::create(['name' => 'Web Admin', 'slug' => 'web-admin', 'guard_name' => 'web']);
Role::create(['name' => 'API Admin', 'slug' => 'api-admin', 'guard_name' => 'api']);
// Query by guard
Role::forGuard('web')->get();
Role::forGuard('api')->get();
Permission::forGuard('api')->get();
13. Permission Groups & Sets
Organize hundreds of permissions logically:
// Groups (categories for UI)
$group = PermissionGroup::createGroup(['name' => 'User Management', 'slug' => 'user-mgmt']);
$permission->assignToGroup('user-mgmt');
// Sets (bundles for quick assignment)
$set = PermissionSet::createSet(['name' => 'Content Manager', 'slug' => 'content-manager']);
$set->assignPermissions(['posts.view', 'posts.create', 'posts.edit', 'posts.publish']);
// Assign entire set to a role
$role->assignPermissionSet('content-manager');
🔐 Laravel Integration
14. Native Gate Integration
All permissions automatically register with Laravel's Gate via Gate::before():
// These all work out of the box:
$user->can('users.edit');
$user->cannot('users.delete');
// In Blade
@can('users.edit')
<button>Edit</button>
@endcan
// In Controllers
Gate::authorize('users.edit');
Gate::allows('users.edit');
Gate::denies('users.delete');
15. Advanced Middleware DSL
Protect routes with a powerful, expressive syntax:
// Single permission
Route::get('/users', fn() => '...')
->middleware('pm:permission:users.view');
// ANY of these (OR logic)
Route::get('/users', fn() => '...')
->middleware('pm:permission:any:users.view|users.list');
// ALL of these (AND logic)
Route::post('/users', fn() => '...')
->middleware('pm:permission:all:users.view|users.create');
// NOT this permission
Route::get('/public', fn() => '...')
->middleware('pm:permission:not:admin.panel');
// Role checks
Route::get('/admin', fn() => '...')
->middleware('pm:role:admin|manager|editor');
// Combined (AND between directives)
Route::get('/reports', fn() => '...')
->middleware(['pm:role:admin', 'pm:permission:reports.view']);
16. Blade Directives (12+)
@role('admin')
<span>Welcome, Admin!</span>
@endrole
@permission('users.edit')
<button>Edit User</button>
@endpermission
@hasanyrole(['admin', 'editor'])
<span>You can edit content</span>
@endhasanyrole
@hasallroles(['verified', 'premium'])
<span>Premium Verified User</span>
@endhasallroles
@unlesspermission('users.delete')
<span class="text-muted">Delete disabled</span>
@endunlesspermission
@canpermission('posts.update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcanpermission
17. Custom Abilities (Policy Integration)
Define complex authorization logic:
PermissionManager::define('posts.update', function ($user, $post) {
return $post->user_id === $user->id
|| $user->hasPermissionTo('posts.edit.any');
});
// Usage
$user->canPermission('posts.update', $post);
🛠️ Developer Experience
18. Explain API (Debugging)
Get detailed explanations of authorization decisions:
$result = PermissionManager::explain($user, 'orders.delete');
// Returns:
[
'allowed' => false,
'ability' => 'orders.delete',
'reason' => 'explicit_user_deny',
'source' => 'direct_permission',
'metadata' => [
'matched_pattern' => 'orders.delete',
'permission_id' => 42,
],
'user' => [
'id' => 1,
'roles' => ['admin', 'editor'],
'direct_permissions' => ['orders.view', '!orders.delete'],
],
]
19. Permission Snapshot
Debug a user's complete authorization state:
$snapshot = PermissionManager::snapshot($user);
// Returns: roles, allow list, deny list, inherited roles, permissions, context
20. Rich CLI Commands (24 total)
# 🩺 System health check
php artisan permission:doctor
# 🌳 View role hierarchy tree
php artisan permission:tree
# ❓ Why was access denied?
php artisan permission:why 42 users.delete
# 📋 Explain permission (JSON output)
php artisan permission:explain 42 users.edit --json
# ✅ Validate configuration
php artisan permission:validate
# ✂️ Prune expired permissions
php artisan permission:prune --days=30
# ⚡ Cache management
php artisan permission:cache:warm
php artisan permission:cache:clear
# 📦 Generate CRUD permissions
php artisan permission:generate-resource users
# 🔄 Sync routes (3 strategies)
php artisan permission:sync-routes --strategy=controller-action
# Standard CRUD
php artisan role:create admin "Administrator"
php artisan permission:create "users.view"
php artisan role:assign-permission admin "users.*"
php artisan user:assign-role 1 admin
php artisan role:export roles.json
php artisan role:import roles.json
Example: permission:why
$ php artisan permission:why 42 users.delete
┌─────────────────────────────────────────────────┐
│ Permission Decision Explanation │
└─────────────────────────────────────────────────┘
User: 42
Ability: users.delete
✗ DENIED
Reason: explicit_user_deny
Source: direct_permission
Details:
- matched_pattern: users.delete
- permission_id: 15
21. Smart Caching
Hierarchical cache invalidation that actually works:
// When a permission changes:
// 1. Permission cache invalidated
// 2. All roles with that permission invalidated
// 3. All users with those roles invalidated
// Manual cache control
PermissionManager::cache()->flushAll();
PermissionManager::cache()->flushUser($userId);
PermissionManager::cache()->flushRole($roleId);
Supports Cache Tags for Redis/Memcached drivers.
22. Testing Helpers
Built-in helpers for application tests:
use HosseinHezami\PermissionManager\Testing\InteractsWithPermissions;
use HosseinHezami\PermissionManager\Testing\PermissionAssertions;
class PostControllerTest extends TestCase
{
use InteractsWithPermissions;
use PermissionAssertions;
public function test_admin_can_edit_posts()
{
$admin = $this->createUserWithRoles(['admin']);
$this->assertHasRole($admin, 'admin');
$this->assertHasPermission($admin, 'posts.edit');
$this->assertDoesNotHavePermission($admin, 'system.config');
}
public function test_unauthorized_gets_403()
{
$user = $this->createUser();
$this->actingAs($user)
->get('/admin/users')
->assertStatus(403);
}
}
23. Fluent Facade API
use HosseinHezami\PermissionManager\Facades\PermissionManager;
// Roles
PermissionManager::roles()->list();
PermissionManager::roles()->create(['slug' => 'admin', 'name' => 'Administrator']);
PermissionManager::role('admin')->assignPermission('users.*');
PermissionManager::role('admin')->inheritFrom('editor');
// Permissions
PermissionManager::permissions()->create('users.view');
PermissionManager::permissions()->sync();
PermissionManager::permissions()->generateResource('posts');
// Users
PermissionManager::user($userId)->assignRole('admin');
PermissionManager::user($userId)->hasPermission('users.edit');
// Authorization
PermissionManager::check($user, 'users.edit');
PermissionManager::explain($user, 'orders.delete');
PermissionManager::snapshot($user);
// Teams
PermissionManager::setTeam($team);
PermissionManager::clearTeam();
// Cache
PermissionManager::cache()->flushAll();
🏗️ Architecture Overview
The Authorization Engine
All permission checks flow through a single, centralized engine:
Request → Middleware / Gate / Blade
↓
AuthorizationManager
↓
┌─────────────────────┐
│ 1. Super Admin? │ → BYPASS
│ 2. User DENY? │ → DENY
│ 3. Role DENY? │ → DENY
│ 4. User ALLOW? │ → ALLOW
│ 5. Role ALLOW? │ → ALLOW
│ 6. Inherited? │ → ALLOW
│ 7. Conditions? │ → EVALUATE
│ 8. Default │ → DENY
└─────────────────────┘
↓
AuthorizationResult
(allowed, reason, source, metadata)
Database Schema (14 Tables)
roles permissions permission_groups
role_permissions user_roles user_permissions
role_inherits permission_sets permission_set_items
teams team_user permission_conditions
permission_audits authorization_logs
Source Structure (73 files)
src/
├── Authorization/ (9 files - Core engine)
├── Console/ (24 files - CLI commands)
├── Events/ (6 files)
├── Exceptions/ (6 files)
├── Handlers/ (2 files)
├── Listeners/ (2 files)
├── Middleware/ (3 files)
├── Models/ (10 files)
├── Proxies/ (2 files)
├── Services/ (2 files)
├── Support/ (5 files)
├── Testing/ (2 files)
├── Traits/ (1 file)
└── Root files (2 files)
💡 Real-World Examples
E-commerce Platform
// Hierarchy: super-admin > admin > seller > customer
$seller->inheritFrom('customer');
$admin->inheritFrom('seller');
// Conditional: sellers can only edit their own products
PermissionCondition::create([
'permission_id' => Permission::findByRoute('products.edit')->id,
'conditions' => [
'field' => 'user.id',
'operator' => '=',
'value' => 'resource.seller_id',
],
]);
Multi-Tenant SaaS
$companyA = Team::createTeam(['name' => 'Company A']);
$companyB = Team::createTeam(['name' => 'Company B']);
$user->joinTeam($companyA);
$user->joinTeam($companyB);
$user->assignRoleForTeam('admin', $companyA);
$user->assignRoleForTeam('viewer', $companyB);
Contractor with Time-Limited Access
$contractor->givePermissionTo('projects.access', 'allow', now()->addDays(30));
$contractor->givePermissionTo('billing.view', 'allow', now()->addDays(7));
// Automatically expires. No cron job needed for checks.
Approval Workflow
// Managers can approve expenses under $1000
PermissionCondition::create([
'permission_id' => Permission::findByRoute('expenses.approve')->id,
'conditions' => [
'all' => [
['field' => 'user.role', 'operator' => '=', 'value' => 'manager'],
['field' => 'resource.amount', 'operator' => '<=', 'value' => 1000],
],
],
]);
🧪 Testing & Quality
The package ships with 141 passing tests and 226 assertions:
✅ Unit Tests → WildcardMatcher, ConditionEvaluator, MiddlewareParser, CacheKeys
✅ Feature Tests → Trait, Models, Middleware, Blade, Commands, Gate, Proxy
✅ Integration Tests → Hierarchy, Teams, ABAC, Expiry, Cache, Audit
All tests run against SQLite in-memory — completely isolated, fast, and safe.
composer test # All 141 tests
composer test-unit # Unit only
composer test-feature # Feature only
composer test-integration # Integration only
📦 Installation & Configuration
Requirements
- PHP 8.2+
- Laravel 10, 11, 12, or 13
Install
composer require hosseinhezami/laravel-permission-manager
Publish & Migrate
php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="config"
php artisan vendor:publish --provider="HosseinHezami\PermissionManager\PermissionManagerServiceProvider" --tag="migrations"
php artisan migrate
Configuration Highlights
// config/permission-manager.php
return [
'cache_duration' => 60,
'wildcards' => true,
'super_admin' => [
'enabled' => true,
'role_slug' => 'super-admin',
'bypass_all' => true,
],
'teams' => ['enabled' => true],
'audit' => ['enabled' => true, 'log_mutations' => true],
'conditions' => ['enabled' => true],
'authorization_logging' => [
'enabled' => false,
'denied_only' => true,
'sample_rate' => 1.0,
],
];
🔗 Links
- 🐙 GitHub: github.com/hosseinhezami/laravel-permission-manager
- 📦 Packagist: packagist.org/packages/hosseinhezami/laravel-permission-manager
📊 Comparison with Spatie Laravel Permission
| Feature | Laravel Permission Manager | Spatie Permission |
|---|---|---|
| RBAC | ✅ | ✅ |
| Direct Permissions | ✅ | ✅ |
| Wildcard Permissions | ✅ (advanced + negation) | ✅ |
| Explicit Allow/Deny | ✅ | ❌ |
| Role Hierarchy | ✅ Multi-level + cycle detection | ❌ |
| Temporary Permissions | ✅ Auto-expiry | ❌ |
| Teams / Multi-Tenancy | ✅ | ✅ |
| Multi-Guard (real) | ✅ guard_name column | ✅ |
| ABAC / Conditions | ✅ JSON engine (no eval) | ❌ |
| Audit Logging | ✅ Built-in | ❌ |
| Authorization Trail | ✅ Configurable | ❌ |
| Explain API | ✅ | ❌ |
| Permission Snapshot | ✅ | ❌ |
| CLI Doctor | ✅ | ❌ |
| CLI Why/Explain | ✅ | ❌ |
| Permission Tree | ✅ | ❌ |
| Resource Generator | ✅ | ❌ |
| Route Sync | ✅ (3 strategies) | ❌ |
| Permission Groups | ✅ | ❌ |
| Permission Sets | ✅ | ❌ |
| Gate Integration | ✅ Native | ✅ |
| Middleware DSL | ✅ any/all/not | ✅ |
| Blade Directives | ✅ 12+ | ✅ |
| Smart Caching | ✅ Hierarchical | ✅ |
| Testing Helpers | ✅ Built-in | ❌ |
| Events | ✅ 6 events | ✅ |
| Import/Export | ✅ JSON | ❌ |
| Tests | 141 | ~300 |
| Laravel 13 Support | ✅ | ⚠️ |
| Backward Compatible | ✅ 100% | ✅ |
💬 Final Thoughts
Laravel Permission Manager v2.0 is not just another permission package. It's a complete enterprise authorization platform that handles everything from simple RBAC to complex ABAC with conditions, multi-tenancy, audit trails, and hierarchical role structures.
Whether you're building a startup MVP or a large-scale SaaS platform, this package scales with your needs — without requiring you to switch packages or write custom authorization logic.
composer require hosseinhezami/laravel-permission-manager
Star ⭐ it on GitHub if you find it useful!
🔗 GitHub Repository · 📦 Packagist
Built with ❤️ by Hossein Hezami · MIT License
Tags: laravel, php, authorization, rbac, abac, permissions, access-control, multi-tenancy, security, opensource, laravel-package, role-management
Top comments (0)