# Laravel Permissions Package ## Introduction Laravel Permissions is a highly optimized role-based access control (RBAC) package designed for Laravel 11 and 12 applications. This package provides a complete permission management system with polymorphic relationships, allowing any model to have roles and permissions. It enables developers to implement fine-grained access control by assigning roles to users and attaching permissions either directly to users or through roles, creating a flexible multi-layered authorization system. The package extends beyond basic RBAC with advanced features including support for multiple authentication guards, wildcard permission patterns, expirable permissions and roles, super admin functionality, and native Laravel Gate integration. Built with performance in mind, it includes advanced caching mechanisms with Redis tag support, database transaction handling, eager loading optimizations, and composite indexes. The package seamlessly integrates with Laravel's existing authorization infrastructure, allowing developers to use familiar methods like `$user->can()` and Blade directives like `@can()` while benefiting from enhanced RBAC capabilities. ## User Model Setup ### Adding the HasRolesAndPermissions Trait ```php assignRole('admin'); $user->givePermissionTo('create-post'); if ($user->hasRole('admin')) { echo "User is an administrator"; } if ($user->hasPermission('create-post')) { echo "User can create posts"; } ``` ## Role Management ### Creating Roles ```php 'Administrator', 'slug' => 'admin', 'description' => 'Administrator role with full access', 'guard_name' => 'web' // Optional, defaults to 'web' ]); // Create multiple roles $editor = Role::create([ 'name' => 'Editor', 'slug' => 'editor', 'description' => 'Content editor role' ]); $moderator = Role::create([ 'name' => 'Moderator', 'slug' => 'moderator', 'description' => 'Community moderator' ]); // Find or create a role $manager = Role::findOrCreate('manager', 'Manager', 'web'); // Find role by slug and guard $webAdmin = Role::findBySlug('admin', 'web'); $apiAdmin = Role::findBySlug('admin', 'api'); ``` ### Assigning Roles to Users ```php assignRole('admin'); // Assign multiple roles by slug $user->assignRole('admin', 'editor'); // Assign role by ID $user->assignRole(1); // Assign using Role model instance $adminRole = Role::where('slug', 'admin')->first(); $user->assignRole($adminRole); // Remove a role from user $user->removeRole('editor'); // Sync roles (removes all existing roles and assigns new ones) $user->syncRoles(['admin', 'moderator']); // Result: User now has only 'admin' and 'moderator' roles ``` ### Assigning Temporary Roles with Expiration ```php assignRoleUntil('premium', now()->addMonth()); // Assign trial role for 7 days $user->assignRoleUntil('trial-user', now()->addDays(7)); // Assign seasonal moderator role until specific date $user->assignRoleUntil('seasonal-mod', Carbon::parse('2025-12-31')); // Using role ID $user->assignRoleUntil(5, now()->addWeeks(2)); // Role checks automatically exclude expired roles if ($user->hasRole('premium')) { echo "Active premium subscription"; } // Returns false after expiration date // Query users with active roles $activePremiumUsers = User::role('premium')->get(); // Only returns users whose premium role hasn't expired ``` ### Checking User Roles ```php hasRole('admin')) { echo "User is an administrator"; } // Check if user has any of multiple roles if ($user->hasAnyRole(['admin', 'editor', 'moderator'])) { echo "User is a staff member"; } // Check if user has all specified roles if ($user->hasAllRoles(['admin', 'verified'])) { echo "User is a verified administrator"; } // Check if user is super admin (has all permissions) if ($user->isSuperAdmin()) { echo "User has unlimited access"; } // Get all user roles $roles = $user->roles; foreach ($roles as $role) { echo $role->name . ": " . $role->description; } ``` ## Permission Management ### Creating Permissions ```php 'Create Post', 'slug' => 'create-post', 'description' => 'Can create new blog posts', 'guard_name' => 'web' ]); $editPost = Permission::create([ 'name' => 'Edit Post', 'slug' => 'edit-post', 'description' => 'Can edit existing posts' ]); $deletePost = Permission::create([ 'name' => 'Delete Post', 'slug' => 'delete-post', 'description' => 'Can delete posts' ]); // Create permissions for different guards $apiCreateUser = Permission::create([ 'name' => 'API Create User', 'slug' => 'create-user', 'guard_name' => 'api' ]); // Find or create permission $publishPost = Permission::findOrCreate('publish-post', 'Publish Post', 'web'); ``` ### Assigning Permissions to Roles ```php first(); $editor = Role::where('slug', 'editor')->first(); // Give single permission to role $admin->givePermissionTo('delete-post'); // Give multiple permissions to role $admin->givePermissionTo('create-post', 'edit-post', 'delete-post'); // Give permissions using Permission models $createPost = Permission::where('slug', 'create-post')->first(); $editPost = Permission::where('slug', 'edit-post')->first(); $editor->givePermissionTo($createPost, $editPost); // Revoke permission from role $editor->revokePermissionTo('delete-post'); // Sync permissions (removes old, adds new) $editor->syncPermissions(['create-post', 'edit-post']); // Check if role has permission if ($admin->hasPermission('delete-post')) { echo "Admins can delete posts"; } // Changes automatically clear cache for all affected users ``` ### Assigning Direct Permissions to Users ```php givePermissionTo('create-post'); // Give multiple permissions $user->givePermissionTo('create-post', 'edit-post', 'publish-post'); // Using Permission model $deletePermission = Permission::where('slug', 'delete-post')->first(); $user->givePermissionTo($deletePermission); // Using permission ID $user->givePermissionTo(3); // Revoke direct permission $user->revokePermissionTo('edit-post'); // Sync direct permissions (replaces all) $user->syncPermissions(['create-post', 'view-analytics']); // Get all user permissions (direct + from roles) $allPermissions = $user->getAllPermissions(); foreach ($allPermissions as $permission) { echo $permission->name . PHP_EOL; } ``` ### Temporary Permissions with Expiration ```php givePermissionToUntil('beta-feature', now()->addDays(30)); // Grant trial premium features for one week $user->givePermissionToUntil('premium-export', now()->addWeek()); // Grant temporary admin access for emergency (1 hour) $user->givePermissionToUntil('emergency-admin', now()->addHour()); // Grant seasonal feature access $user->givePermissionToUntil('holiday-theme', now()->endOfMonth()); // Permission checks automatically filter expired permissions if ($user->hasPermission('beta-feature')) { echo "User has active beta access"; } // Returns false after expiration // Expired permissions don't grant access $user->can('beta-feature'); // false after expiration date ``` ### Checking User Permissions ```php hasPermission('create-post')) { echo "User can create posts"; } // Check if user has any of multiple permissions if ($user->hasAnyPermission(['create-post', 'create-article'])) { echo "User can create content"; } // Check if user has all specified permissions if ($user->hasAllPermissions(['edit-post', 'publish-post'])) { echo "User can edit and publish"; } // Using Laravel's native Gate (requires gate.enabled = true) if ($user->can('delete-post')) { echo "User authorized to delete posts"; } // Get all permissions (direct + from roles) $permissions = $user->getAllPermissions(); echo "User has " . $permissions->count() . " permissions"; // Get only direct permissions $directPermissions = $user->permissions; // Get permissions from roles $rolePermissions = $user->roles->load('permissions') ->pluck('permissions') ->flatten() ->unique('id'); ``` ## Wildcard Permissions ### Using Wildcard Patterns for Flexible Access Control ```php 'All Post Permissions', 'slug' => 'posts.*', 'description' => 'Grants all post-related permissions' ]); $role = Role::where('slug', 'content-manager')->first(); $role->givePermissionTo('posts.*'); $user = User::find(1); $user->assignRole('content-manager'); // Now user has access to all post-related permissions $user->hasPermission('posts.create'); // true $user->hasPermission('posts.edit'); // true $user->hasPermission('posts.delete'); // true $user->hasPermission('posts.publish'); // true $user->hasPermission('posts.archive'); // true // Works with nested patterns $user->givePermissionTo('admin.*'); $user->hasPermission('admin.users.create'); // true $user->hasPermission('admin.settings.update'); // true // Mixed wildcard and specific permissions $user->givePermissionTo('comments.moderate'); $user->givePermissionTo('reports.*'); $user->hasPermission('comments.moderate'); // true $user->hasPermission('reports.view'); // true $user->hasPermission('reports.export'); // true ``` ## Super Admin Role ### Configuring and Using Super Admin ```php 'Super Administrator', 'slug' => 'super-admin', 'description' => 'Has all permissions automatically' ]); $user = User::find(1); $user->assignRole('super-admin'); // Super admin check if ($user->isSuperAdmin()) { echo "Unlimited access granted"; } // Super admin has ALL permissions without explicit assignment $user->hasPermission('any-permission'); // true $user->hasPermission('delete-everything'); // true $user->hasPermission('system-configuration'); // true $user->can('perform-any-action'); // true // Super admin bypasses all permission checks if ($user->isSuperAdmin()) { // Skip detailed permission checking // Grant full access to all features return response()->json(['access' => 'unlimited']); } // Regular permission flow for non-super admins if ($user->hasPermission('specific-action')) { // Normal authorization logic } ``` ## Query Scopes for Filtering Users ### Finding Users by Roles and Permissions ```php get(); // Get users with any of multiple roles $staff = User::role(['admin', 'editor', 'moderator'])->get(); // Get users with specific permission $authors = User::permission('create-post')->get(); // Get users with any of multiple permissions $contentCreators = User::permission(['create-post', 'create-article'])->get(); // Get users WITHOUT specific role $regularUsers = User::withoutRole('banned')->get(); // Get users WITHOUT specific permission $limitedUsers = User::withoutPermission('delete-post')->get(); // Combine scopes with query builder $activeEditors = User::role('editor') ->permission('create-post') ->where('status', 'active') ->where('email_verified_at', '!=', null) ->orderBy('created_at', 'desc') ->get(); // Complex filtering $seniorStaff = User::role(['admin', 'senior-editor']) ->withoutRole('suspended') ->permission('manage-users') ->whereDate('created_at', '<', now()->subYear()) ->paginate(20); // Count users by role $adminCount = User::role('admin')->count(); $editorCount = User::role('editor')->count(); ``` ## Middleware Protection ### Protecting Routes with Role Middleware ```php middleware('role:admin'); // Multiple roles (user needs at least one) Route::get('/staff/panel', [AdminController::class, 'staff']) ->middleware('role:admin|editor|moderator'); // Protecting route groups Route::middleware(['role:admin'])->group(function () { Route::get('/admin/users', [AdminController::class, 'users']); Route::get('/admin/settings', [AdminController::class, 'settings']); Route::post('/admin/config', [AdminController::class, 'updateConfig']); }); // Multiple middleware layers Route::middleware(['auth', 'role:admin']) ->prefix('admin') ->group(function () { Route::resource('posts', PostController::class); }); // Resource routes with role protection Route::middleware('role:editor')->group(function () { Route::resource('articles', 'ArticleController')->except(['destroy']); }); ``` ### Protecting Routes with Permission Middleware ```php middleware('permission:create-post'); // Multiple permissions (user needs at least one) Route::put('/posts/{post}', [PostController::class, 'update']) ->middleware('permission:edit-post|edit-own-post'); // Delete requires specific permission Route::delete('/posts/{post}', [PostController::class, 'destroy']) ->middleware('permission:delete-post'); // Permission-based route groups Route::middleware(['permission:manage-posts'])->group(function () { Route::get('/posts', [PostController::class, 'index']); Route::post('/posts', [PostController::class, 'store']); Route::put('/posts/{id}', [PostController::class, 'update']); }); // Combining authentication, role, and permission checks Route::middleware(['auth', 'role:admin', 'permission:delete-post']) ->delete('/posts/{post}', [PostController::class, 'destroy']); // Different permissions for different methods Route::get('/posts', [PostController::class, 'index']) ->middleware('permission:view-posts'); Route::post('/posts', [PostController::class, 'store']) ->middleware('permission:create-post'); Route::delete('/posts/{post}', [PostController::class, 'destroy']) ->middleware('permission:delete-post'); ``` ### Authentication Middleware ```php middleware('check.auth'); // Combining with other middleware Route::middleware(['check.auth', 'role:admin'])->group(function () { Route::get('/admin/panel', function () { return view('admin.panel'); }); }); // Response configuration in config/permissions.php 'middleware' => [ 'unauthenticated_response' => [ 'type' => 'redirect', // 'json', 'redirect', 'abort' 'redirect_to' => '/login', 'abort_code' => 401, 'json_message' => 'Unauthenticated.', ], ]; ``` ## Cache Management ### Manual Cache Control ```php clearUserCache($userId); // Clear cache for specific role $roleId = 5; $cache->clearRoleCache($roleId); // Clear cache for all users who have a specific role $roleId = 3; $cache->clearAffectedUsersCaches($roleId); // Flush all permission caches $cache->flush(); // Check cache configuration if ($cache->isEnabled()) { echo "Caching is enabled"; } if ($cache->isRoleCacheEnabled()) { echo "Role caching is active"; } if ($cache->isPermissionCacheEnabled()) { echo "Permission caching is active"; } if ($cache->usesTags()) { echo "Using Redis cache tags for efficient clearing"; } // Manual cache operations $key = 'custom_permission_key'; $cache->put($key, $value, 3600); $cached = $cache->get($key); $cache->forget($key); // Remember pattern $permissions = $cache->remember( 'user_1_permissions', function () use ($user) { return $user->getAllPermissions()->pluck('slug')->toArray(); }, 3600 ); ``` ### Automatic Cache Invalidation ```php assignRole('admin'); // Clears user 1's cache // 2. Removing roles from users $user->removeRole('editor'); // Clears user 1's cache // 3. Syncing roles $user->syncRoles(['admin', 'moderator']); // Clears user 1's cache // 4. Giving permissions to users $user->givePermissionTo('create-post'); // Clears user 1's cache // 5. Revoking permissions from users $user->revokePermissionTo('edit-post'); // Clears user 1's cache // 6. Modifying role permissions $role = Role::find(2); $role->givePermissionTo('delete-post'); // Clears cache for ALL users with role 2 // 7. Removing permissions from roles $role->revokePermissionTo('publish-post'); // Clears cache for ALL users with this role // 8. Syncing role permissions $role->syncPermissions(['create-post', 'edit-post']); // Clears cache for ALL users with this role // 9. Deleting roles $role->delete(); // Clears cache for ALL users who had this role // 10. Deleting or updating permissions $permission = Permission::find(5); $permission->delete(); // Flushes all permission caches ``` ## Multiple Guards Support ### Working with Different Authentication Guards ```php 'Web Administrator', 'slug' => 'admin', 'guard_name' => 'web' ]); $apiAdmin = Role::create([ 'name' => 'API Administrator', 'slug' => 'admin', 'guard_name' => 'api' ]); $backofficeManager = Role::create([ 'name' => 'Backoffice Manager', 'slug' => 'manager', 'guard_name' => 'backoffice' ]); // Create permissions for specific guards $webCreatePost = Permission::create([ 'name' => 'Create Post (Web)', 'slug' => 'create-post', 'guard_name' => 'web' ]); $apiCreatePost = Permission::create([ 'name' => 'Create Post (API)', 'slug' => 'create-post', 'guard_name' => 'api' ]); // Query by guard $webRoles = Role::forGuard('web')->get(); $apiPermissions = Permission::forGuard('api')->get(); // Find by slug and guard $webAdminRole = Role::findBySlug('admin', 'web'); $apiAdminRole = Role::findBySlug('admin', 'api'); // Find or create with guard $moderator = Role::findOrCreate('moderator', 'Moderator', 'api'); ``` ## Controller Integration Examples ### Complete Controller with Permission Checks ```php 'create-post'], [ 'name' => 'Create Post', 'description' => 'Can create posts' ]), Permission::firstOrCreate(['slug' => 'edit-post'], [ 'name' => 'Edit Post', 'description' => 'Can edit posts' ]), Permission::firstOrCreate(['slug' => 'delete-post'], [ 'name' => 'Delete Post', 'description' => 'Can delete posts' ]), ]; // Create admin role $admin = Role::firstOrCreate(['slug' => 'admin'], [ 'name' => 'Administrator', 'description' => 'Full access' ]); // Assign all permissions to admin $admin->syncPermissions(['create-post', 'edit-post', 'delete-post']); // Create editor role with limited permissions $editor = Role::firstOrCreate(['slug' => 'editor'], [ 'name' => 'Editor', 'description' => 'Content editor' ]); $editor->syncPermissions(['create-post', 'edit-post']); return response()->json([ 'message' => 'Setup complete', 'roles' => Role::with('permissions')->get() ]); } /** * Assign role to user with validation */ public function assignRole(Request $request) { $request->validate([ 'user_id' => 'required|exists:users,id', 'role' => 'required|string' ]); $user = User::findOrFail($request->user_id); // Check if requester has permission if (!auth()->user()->hasPermission('manage-users')) { return response()->json([ 'message' => 'Unauthorized' ], 403); } $user->assignRole($request->role); return response()->json([ 'message' => 'Role assigned successfully', 'user' => $user->load('roles') ]); } /** * Check user permissions dynamically */ public function checkAccess(Request $request) { $user = User::findOrFail($request->user_id); return response()->json([ 'user' => $user->name, 'roles' => $user->roles->pluck('name'), 'direct_permissions' => $user->permissions->pluck('name'), 'all_permissions' => $user->getAllPermissions()->pluck('name'), 'is_admin' => $user->hasRole('admin'), 'is_super_admin' => $user->isSuperAdmin(), 'can_delete' => $user->hasPermission('delete-post') ]); } /** * Grant temporary access */ public function grantTemporaryAccess(Request $request) { $request->validate([ 'user_id' => 'required|exists:users,id', 'permission' => 'required|string', 'expires_in_days' => 'required|integer|min:1|max:365' ]); $user = User::findOrFail($request->user_id); $expiresAt = now()->addDays($request->expires_in_days); $user->givePermissionToUntil($request->permission, $expiresAt); return response()->json([ 'message' => 'Temporary access granted', 'expires_at' => $expiresAt->toDateTimeString() ]); } /** * Clear caches */ public function clearCache(Request $request) { $cache = app(\Saeedvir\LaravelPermissions\Services\PermissionCache::class); if ($request->has('user_id')) { $cache->clearUserCache($request->user_id); return response()->json(['message' => 'User cache cleared']); } if ($request->has('role_id')) { $cache->clearAffectedUsersCaches($request->role_id); return response()->json(['message' => 'Role cache cleared']); } $cache->flush(); return response()->json(['message' => 'All caches cleared']); } } ``` ## Configuration and Environment Setup ### Environment Variables Configuration ```bash # Database Configuration PERMISSION_DB_CONNECTION=mysql PERMISSION_DB_NAME=laravel_permission # Cache Settings (Recommended: all enabled) PERMISSION_CACHE_ENABLED=true PERMISSION_CACHE_ROLES=true PERMISSION_CACHE_PERMISSIONS=true PERMISSION_CACHE_EXPIRATION=3600 PERMISSION_CACHE_STORE=redis PERMISSION_CACHE_USE_TAGS=true # Optional Features (Enable as needed) PERMISSION_GUARDS_ENABLED=false PERMISSION_WILDCARD_ENABLED=false PERMISSION_SUPER_ADMIN_ENABLED=false PERMISSION_SUPER_ADMIN_SLUG=super-admin PERMISSION_EXPIRABLE_ENABLED=false PERMISSION_EXPIRABLE_ROLES_ENABLED=false # Laravel Integration (Recommended: enabled) PERMISSION_GATE_ENABLED=true # Performance Settings (Recommended: enabled) PERMISSION_USE_TRANSACTIONS=true ``` ### Programmatic Configuration Access ```php