A comprehensive and flexible Laravel package for advanced permission management. This package provides a robust system for managing roles, permissions, groups, and entity-specific abilities.
Core Functionality:
-
Role-Based Access Control (RBAC): Define custom roles with specific permission sets. Roles can be assigned to users to manage access levels efficiently.
-
Permission System: Implement fine-grained permissions using a code-based system (e.g.,
posts.create,users.edit). Permissions are global entities that can be assigned to roles and groups. Supports wildcard permissions for flexible access patterns. -
Group Management (Optional): Organize users into groups. Groups can have their own permission sets, allowing for efficient permission management when multiple users need the same access level. This feature is optional and can be skipped if not needed.
-
Entity-Specific Abilities: Grant or deny permissions for specific model instances (e.g., allowing a user to edit a particular post but not others). This provides the most granular level of access control.
-
Caching & Performance: Intelligent caching system to optimize permission checks, reducing database queries and improving application performance.
-
Audit Logging: Optional comprehensive audit trail that logs all permission-related actions including role assignments and permission changes.
-
Laravel Integration: Seamlessly integrates with Laravel's built-in authorization system, including Policies, Blade directives, and middleware for route protection.
- âś… Roles & Permissions: Flexible role system with granular permissions
- âś… Direct Permissions: Assign or revoke permissions directly to users, overriding role permissions
- âś… Groups (Optional): Organize users into groups with shared permissions
- âś… Abilities: (Optional) Entity-specific permissions for individual models
- âś… Smart Caching: Caching system to optimize permission checks
- âś… Audit Logging: Complete action logging (optional)
- âś… Blade Directives: Blade directives for permission checks in views
- âś… Policies: Integration with Laravel's Policy system
- âś… Middleware: Middleware for route protection
- âś… Artisan Commands: CLI tools for management
- âś… Events: Event system for permission changes (RoleAssigned, RoleRemoved, AbilityGranted, AbilityRevoked, PermissionGranted, PermissionRevoked)
- âś… Validation: Automatic validation of permission codes
- âś… Performance: Optimized queries with eager loading
- PHP >= 8.1
- Laravel 8.x, 9.x, 10.x, 11.x or 12.x
composer require squareetlabs/laravel-simple-permissionsphp artisan vendor:publish --provider="Squareetlabs\LaravelSimplePermissions\SimplePermissionsServiceProvider"This will publish:
config/simple-permissions.php- Configuration file- Database migrations
Add the HasPermissions trait to your User model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Squareetlabs\LaravelSimplePermissions\Traits\HasPermissions;
class User extends Model
{
use HasPermissions;
// ... rest of your code
}
⚠️ IMPORTANT: Always do backups before running migrations.
php artisan migrateNote
If you wish to use custom foreign keys and table names, modify config/simple-permissions.php before running migrations.
You can enable or disable optional features (Groups and Abilities) via configuration. When disabled, related migrations won't be published and related functionality will be skipped.
Configure in config/simple-permissions.php or via environment variables:
# Disable groups feature
SIMPLE_PERMISSIONS_GROUPS_ENABLED=false
# Disable abilities feature
SIMPLE_PERMISSIONS_ABILITIES_ENABLED=falseOr in config/simple-permissions.php:
'features' => [
'groups' => [
'enabled' => env('SIMPLE_PERMISSIONS_GROUPS_ENABLED', true),
],
'abilities' => [
'enabled' => env('SIMPLE_PERMISSIONS_ABILITIES_ENABLED', true),
],
],Note
Important: Configure these settings before publishing migrations. If you disable a feature after migrations have been published, you'll need to manually remove the related migration files or tables.
Migrations:
- Essential:
create_permissions_table.php,create_roles_table.php,create_role_user_table.php,create_permission_user_table.php,create_entity_permission_table.php - Optional - Groups (
create_groups_table.php,create_group_user_table.php): Only published iffeatures.groups.enabledistrue - Optional - Abilities (
create_abilities_table.php,create_entity_ability_table.php): Only published iffeatures.abilities.enabledistrue - Optional - Audit Logging (
create_audit_logs_table.php): Always published (table creation is handled by AuditService)
Note
The permission_user table is essential and always created. It allows direct permission assignments to users, overriding role-based permissions.
To improve performance, enable caching in .env:
SIMPLE_PERMISSIONS_CACHE_ENABLED=true
SIMPLE_PERMISSIONS_CACHE_DRIVER=redis
SIMPLE_PERMISSIONS_CACHE_TTL=3600To log all permission actions:
SIMPLE_PERMISSIONS_AUDIT_ENABLED=true
SIMPLE_PERMISSIONS_AUDIT_LOG_CHANNEL=stackThe configuration file config/simple-permissions.php contains all options:
'models' => [
'user' => App\Models\User::class,
// ... other models
],'cache' => [
'enabled' => env('SIMPLE_PERMISSIONS_CACHE_ENABLED', true),
'driver' => env('SIMPLE_PERMISSIONS_CACHE_DRIVER', 'redis'),
'ttl' => env('SIMPLE_PERMISSIONS_CACHE_TTL', 3600),
'prefix' => 'simple_permissions',
'tags' => true,
],use Squareetlabs\LaravelSimplePermissions\Support\Facades\SimplePermissions;
// Create permissions
$viewPost = SimplePermissions::model('permission')::create(['code' => 'posts.view', 'name' => 'View Posts']);
$createPost = SimplePermissions::model('permission')::create(['code' => 'posts.create', 'name' => 'Create Posts']);
// Create role
$adminRole = SimplePermissions::model('role')::create(['code' => 'admin', 'name' => 'Administrator']);
// Assign permissions to role
$adminRole->permissions()->attach([$viewPost->id, $createPost->id]);// Assign role to user
$user->assignRole('admin');
// Remove role from user
$user->removeRole('admin');
// Sync roles (replaces all existing roles)
$user->syncRoles(['admin', 'editor']);You can assign or revoke permissions directly to users, overriding role-based permissions:
// Give a permission directly to a user (even if their role doesn't have it)
$user->givePermission('posts.create');
// Revoke a permission directly from a user (even if their role has it)
$user->revokePermission('posts.edit');
// Remove a direct permission assignment (returns to role-based permissions)
$user->removePermission('posts.delete');
// Sync direct permissions (replaces all existing direct permissions)
$user->syncPermissions(['posts.create', 'posts.view']);Priority Order:
- Direct permissions (granted or revoked) have the highest priority
- If a permission is directly revoked, the user won't have it even if their role has it
- If a permission is directly granted, the user will have it even if their role doesn't have it
- If no direct assignment exists, role and group permissions apply
// Check if user has a permission (direct or via role/group)
if ($user->hasPermission('posts.create')) {
// User can create posts
}
// Check if user has a role
if ($user->hasRole('admin')) {
// User is admin
}
// Check specific ability on an entity
if ($user->hasAbility('edit', $post)) {
// User can edit this specific post
}The HasPermissions trait provides the following methods:
// Check if user has a role (or roles)
// $require = true: all roles are required
// $require = false: at least one of the roles
$user->hasRole('admin', $require = false)
$user->hasRole(['admin', 'editor'], $require = false)
// Check if user has a permission (or permissions)
// $require = true: all permissions are required
// $require = false: at least one of the permissions
$user->hasPermission('posts.create', $require = false)
$user->hasPermission(['posts.create', 'posts.edit'], $require = false)
// Check if user has an ability on an entity
$user->hasAbility('posts.edit', $post)
// Allow ability for user on an entity
$user->allowAbility('posts.edit', $post)
// Forbid ability for user on an entity
$user->forbidAbility('posts.edit', $post)
// Remove ability from user
$user->removeAbility('posts.edit', $post)
// Direct permissions (override role permissions)
$user->givePermission('posts.create') // Grant permission directly
$user->revokePermission('posts.edit') // Revoke permission directly (even if role has it)
$user->removePermission('posts.delete') // Remove direct assignment (return to role-based)
$user->syncPermissions(['perm1', 'perm2']) // Sync direct permissionsYou can use wildcards for permissions:
posts.*- All permissions starting withposts.*- All permissions (if enabled in config)
// Check multiple permissions (OR)
if ($user->hasPermission(['posts.create', 'posts.edit'], false)) {
// User can create OR edit posts
}
// Check multiple permissions (AND)
if ($user->hasPermission(['posts.create', 'posts.edit'], true)) {
// User can create AND edit posts
}Note
Abilities are optional. Enable/disable via SIMPLE_PERMISSIONS_ABILITIES_ENABLED in your .env file or config/simple-permissions.php. When disabled, ability-related migrations won't be published. The hasAbility() method will fall back to checking global permissions, and methods like allowAbility(), forbidAbility(), and removeAbility() will throw an exception.
Abilities allow specific permissions for individual entities.
You can use helper methods for easier ability management:
// Allow user to edit a specific post
$user->allowAbility('posts.edit', $post);
// Forbid user to edit a specific post
$user->forbidAbility('posts.edit', $post);
// Remove ability from user
$user->removeAbility('posts.edit', $post);Or use the direct approach:
use Squareetlabs\LaravelSimplePermissions\Support\Facades\SimplePermissions;
// Create a permission first
$permission = SimplePermissions::model('permission')::create(['code' => 'posts.edit']);
// Create an ability for a specific entity
$ability = SimplePermissions::model('ability')::create([
'permission_id' => $permission->id,
'title' => 'Edit Post #1',
'entity_id' => $post->id,
'entity_type' => get_class($post),
]);
// Allow user to edit a specific post
$ability->users()->attach($user, ['forbidden' => false]);
// Forbid user to edit a specific post
$ability->users()->attach($user, ['forbidden' => true]);
// Remove ability from user
$ability->users()->detach($user);if ($user->hasAbility('posts.edit', $post)) {
// User can edit this specific post
}Note
Groups are optional. Enable/disable via SIMPLE_PERMISSIONS_GROUPS_ENABLED in your .env file or config/simple-permissions.php. When disabled, group-related migrations won't be published and group functionality will be skipped automatically.
Groups allow organizing users with shared permissions. This is useful when multiple users need the same set of permissions and you want to manage them collectively.
use Squareetlabs\LaravelSimplePermissions\Support\Facades\SimplePermissions;
// Create group
$group = SimplePermissions::model('group')::create(['code' => 'moderators', 'name' => 'Moderators']);
// Assign permissions to group
$permission = SimplePermissions::model('permission')::where('code', 'posts.moderate')->first();
$group->permissions()->attach($permission);
// Add users to group
$group->users()->attach($user);
// Remove users from group
$group->users()->detach($user);The package provides middleware for route protection.
// Check role
Route::middleware(['role:admin'])->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
});
// Check permission
Route::middleware(['permission:posts.create'])->group(function () {
Route::post('/posts', [PostController::class, 'store']);
});
// Check ability
// Format: ability:action,entity_class,route_parameter_name
Route::middleware(['ability:edit,App\Models\Post,post_id'])->group(function () {
Route::put('/posts/{post_id}', [PostController::class, 'update']);
});// User must have admin OR root
Route::middleware(['role:admin|root'])->group(function () {
// ...
});The package includes Blade directives for permission checks in views:
{{-- Check role --}}
@role('admin')
<button>Admin Panel</button>
@endrole
{{-- Check permission --}}
@permission('posts.create')
<a href="{{ route('posts.create') }}">New Post</a>
@endpermission
{{-- Check ability --}}
@ability('edit', $post)
<button>Edit Post</button>
@endabilityThe package integrates with Laravel's Policy system.
php artisan permissions:policy PostPolicy --model=Post// In a controller
if ($user->can('view', $post)) {
// User can view the post
}
// In a view
@can('update', $post)
<button>Edit</button>
@endcanThe package dispatches events when permissions change, allowing you to hook into these actions:
RoleAssigned: Dispatched when a role is assigned to a userRoleRemoved: Dispatched when a role is removed from a userPermissionGranted: Dispatched when a permission is granted directly to a userPermissionRevoked: Dispatched when a permission is revoked directly from a userAbilityGranted: Dispatched when an ability is granted to a userAbilityRevoked: Dispatched when an ability is revoked from a user
use Squareetlabs\LaravelSimplePermissions\Events\RoleAssigned;
use Squareetlabs\LaravelSimplePermissions\Events\PermissionGranted;
use Squareetlabs\LaravelSimplePermissions\Events\AbilityGranted;
// In your EventServiceProvider
protected $listen = [
RoleAssigned::class => [
// Your listeners here
],
PermissionGranted::class => [
// Your listeners here
],
AbilityGranted::class => [
// Your listeners here
],
];use Squareetlabs\LaravelSimplePermissions\Events\RoleAssigned;
class LogRoleAssignment
{
public function handle(RoleAssigned $event)
{
// Log the role assignment
Log::info("User {$event->user->id} was assigned role {$event->role->code}");
}
}The package includes several useful commands:
// List all roles
php artisan permissions:roles
// Show role details
php artisan permissions:show-role {role}
// List all permissions
php artisan permissions:list
// Sync permissions from configuration
php artisan permissions:sync
// Export permissions
php artisan permissions:export --format=json
// Import permissions
php artisan permissions:import --file=permissions.json
// Clear permissions cache
php artisan permissions:clear-cache
// Generate a policy
php artisan permissions:policy PostPolicy --model=Post