Role-Based Access Control in Laravel: A Complete Guide with LaraDashboard
Authorization is one of those features that seems straightforward until you actually build it. You start with a simple `is_admin` boolean, then you need editors, moderators, managers, and suddenly you are maintaining a tangled web of conditional checks scattered across your controllers.
Lara Dashboard ships with a complete role-based access control system built on top of Spatie's laravel-permission package, integrated with Laravel's native gates and policies. This guide walks through how it all works together.
The Foundation: Roles and Permissions
Lara Dashboard uses a two-tier authorization model:
- Permissions define individual capabilities: posts.create, users.delete, settings.edit
- Roles group permissions together: an Editor role might have posts.create and posts.edit but not users.delete
Users are assigned one or more roles, and each role carries a set of permissions. When a user attempts an action, the system checks whether any of their roles include the required permission.
Creating Roles from the Admin Panel
The admin panel provides a visual interface for managing roles. Create a new role, give it a descriptive name, and check the permissions it should include. Permissions are organized by feature area (Users, Posts, Settings, etc.), making it easy to build coherent role definitions.
Assigning Roles to Users
Each user profile includes a role assignment section. A user can hold multiple roles simultaneously. If a user is both an Editor and a Support Agent, they inherit the combined permissions of both roles.
How Authorization Checks Work
Lara Dashboard enforces authorization at multiple layers, so a missed check in one place does not expose a vulnerability elsewhere.
Middleware Layer
Route groups are protected by middleware that checks for specific permissions before the request reaches the controller:
Route::middleware(['permission:posts.create'])->group(function () { Route::get('/posts/create', [PostController::class, 'create']);
Route::post('/posts', [PostController::class, 'store']);
});
Controller Layer
Inside controllers, authorization checks use Laravel's `authorize()` method, which delegates to policies:
public function update(UpdatePostRequest $request, Post $post) { $this->authorize('update', $post);
// proceed with update
}
Policy Layer
Policies define the rules for each model. A PostPolicy might allow users to edit their own posts but restrict deletion to administrators:
class PostPolicy { public function update(User $user, Post $post): bool {
return $user->hasPermissionTo('posts.edit') || $post->author_id === $user->id;
}
public function delete(User $user, Post $post): bool {
return $user->hasPermissionTo('posts.delete');
}
}
Blade Directive Layer
In templates, use Blade directives to conditionally render UI elements based on the current user's permissions:
@can('posts.create') <a href="{{ route('posts.create') }}">
New Post
</a>
@endcan
@role('super-admin')
<a href="{{ route('settings.index') }}">
System Settings
</a>
@endrole
This ensures that users only see actions they are authorized to perform, reducing confusion and preventing accidental unauthorized requests.