directorytree/authorization
Native, easy role & permission management for Laravel. Adds migrations and an Authorizable trait to your User model for role/permission checks, optional custom migrations/models, caching, gate registration, middleware, and testing support.
Installation:
composer require directorytree/authorization
php artisan migrate
This creates the roles, permissions, and role_user/permission_user/permission_role tables.
Attach Trait:
Add Authorizable trait to your User model:
use DirectoryTree\Authorization\Traits\Authorizable;
class User extends Authenticatable { use Authorizable; }
First Use Case: Grant a permission to a role and assign it to a user:
// Create a permission
$permission = \DirectoryTree\Authorization\Permission::create([
'name' => 'users.create',
'label' => 'Create Users'
]);
// Create a role and assign the permission
$admin = \DirectoryTree\Authorization\Role::create(['name' => 'admin']);
$admin->grant($permission);
// Assign role to user
$user->roles()->save($admin);
Check Permissions:
Use Laravel’s native can() method:
if (auth()->user()->can('users.create')) {
// User has permission
}
Define Roles:
$admin = Role::create(['name' => 'admin', 'label' => 'Administrator']);
$editor = Role::create(['name' => 'editor', 'label' => 'Editor']);
Assign Permissions to Roles:
$admin->grant(['users.create', 'users.edit', 'users.delete']);
$editor->grant(['users.create', 'users.edit']);
Assign Roles to Users:
$user->roles()->save($admin);
$user->roles()->save($editor);
Check Roles in Middleware/Views:
// Middleware (Kernel.php)
'role' => \DirectoryTree\Authorization\Middleware\RoleMiddleware::class,
// Route
Route::get('/admin', function () {
// ...
})->middleware('role:admin');
// Blade
@role('admin')
<div>Admin Content</div>
@endrole
Grant Permissions Directly to Users:
$user->grant(['posts.create', 'posts.edit']);
Check Permissions in Controllers:
public function create(PostRequest $request)
{
$this->authorize('posts.create'); // Throws 403 if unauthorized
// ...
}
Use Middleware for Routes:
// Kernel.php
'permission' => \DirectoryTree\Authorization\Middleware\PermissionMiddleware::class,
// Route
Route::post('/posts', [PostController::class, 'store'])
->middleware('permission:posts.create');
Combine both for granular control:
// Assign role + direct permissions
$user->roles()->save($admin);
$user->grant('posts.publish'); // Extra permission beyond role
Use Laravel’s DatabaseSeeder to populate roles/permissions:
public function run()
{
$admin = Role::create(['name' => 'admin']);
$admin->grant(['users.*', 'posts.*']);
$user = User::first();
$user->roles()->save($admin);
}
Generate permissions dynamically (e.g., for CRUD operations):
function generateCrudPermissions($resource)
{
return [
"{$resource}.create",
"{$resource}.read",
"{$resource}.update",
"{$resource}.delete",
];
}
$admin->grant(generateCrudPermissions('users'));
Leverage caching for performance (default: daily expiry):
// Customize cache in AuthServiceProvider
Authorization::cacheExpiresIn(now()->addHours(12));
Set up PermissionRegistrar in TestCase:
protected function setUp(): void
{
parent::setUp();
app(PermissionRegistrar::class)->register();
}
public function test_user_can_create_post()
{
$user = User::factory()->create();
$user->grant('posts.create');
$this->actingAs($user)
->post('/posts', ['title' => 'Test'])
->assertOk();
}
Cache Invalidation:
// Manually clear cache if needed
cache()->forget(Authorization::cacheKey());
grant()/revoke() methods (they auto-flush cache).Permission Naming Conflicts:
users.create vs user.create).Middleware Order:
permission middleware must run after auth middleware.Kernel.php:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\Authenticate::class,
\DirectoryTree\Authorization\Middleware\PermissionMiddleware::class,
],
];
Trait Overrides:
Authorizable trait may break functionality if not careful.Authorization::useUserModel() to customize models instead.Gate Registration:
Authorization::disableGateRegistration()) breaks can() checks.Check Cached Permissions:
// Inspect cached permissions
dd(cache()->get(Authorization::cacheKey()));
Log Permission Checks:
// Override hasPermission in User model
public function hasPermission($permission)
{
\Log::info("Checking permission: {$permission} for user: {$this->id}");
return parent::hasPermission($permission);
}
Verify Middleware:
php artisan route:list to check middleware order.dd(request()->user()) in middleware to debug user data.Database Issues:
php artisan migrate:fresh if migrations fail.Custom Permission Logic:
Override hasPermission in your User model:
public function hasPermission($permission)
{
if ($permission === 'super.admin') {
return $this->id === 1; // Only allow ID 1
}
return parent::hasPermission($permission);
}
Custom Storage:
Use Authorization::usePermissionModel() to store permissions in a custom table (e.g., Redis):
Authorization::usePermissionModel(
\App\Models\RedisPermission::class
);
Event Listeners: Listen for permission changes:
// app/Providers/EventServiceProvider
protected $listen = [
\DirectoryTree\Authorization\Events\PermissionGranted::class =>
\App\Listeners\LogPermissionChange::class,
];
API Responses: Return user permissions in API responses:
return response()->json([
'user' => auth()->user(),
'permissions' => auth()->user()->permissions->pluck('name'),
]);
Bulk Operations:
Use syncPermissions for bulk updates (avoids N+1 queries):
$user->permissions()->sync(['users.create', 'users.edit']);
How can I help you explore Laravel packages today?