Role-Based Access Control (RBAC)
Also Known As
TL;DR
Explanation
RBAC assigns permissions to named roles (admin, editor, viewer, moderator) and assigns one or more roles to each user. When a user attempts an action, the system checks whether any of the user's roles includes the required permission. This is more maintainable than direct user-permission assignment (ACL) because changing what a role can do updates all users with that role simultaneously. Implementation patterns: flat RBAC (roles have permissions, users have roles); hierarchical RBAC (admin inherits all editor permissions); constrained RBAC (separation of duty — no user can have conflicting roles like 'requester' and 'approver'). In PHP, RBAC is implemented directly in framework Gate/Policy systems (Laravel Gates, Symfony Voters) or via libraries (spatie/laravel-permission for Laravel). The alternative to RBAC is Attribute-Based Access Control (ABAC) which evaluates policies based on attributes of the user, resource, and environment — more flexible but more complex.
Common Misconception
Why It Matters
Common Mistakes
- Checking roles in views instead of controllers or middleware — business rules belong in the access control layer, not templates.
- Using string comparison for role checks without normalising case — 'Admin' !== 'admin'; use constants or enums for role names.
- Not checking authorisation on every route — a missing auth check on one endpoint is a privilege escalation vulnerability.
- Over-engineering with ABAC when RBAC is sufficient — start with roles and permissions; only add attribute-based policies when RBAC cannot express the required rules.
Code Examples
// Ad-hoc checks scattered everywhere — unmaintainable
class PostController {
public function publish(Post $post) {
if ($user->type !== 'admin' && $user->type !== 'editor') {
abort(403);
}
// What if 'moderator' should also publish?
// Must find every check and update it
}
public function delete(Post $post) {
if ($user->type !== 'admin') abort(403); // different check!
}
}
// Centralised RBAC — change roles once, applies everywhere
// Migration: roles table, permissions table, role_user pivot
// Define permissions once
// Role 'editor' => ['publish-posts', 'edit-posts']
// Role 'admin' => ['publish-posts', 'edit-posts', 'delete-posts']
class PostController {
public function publish(Post $post) {
$this->authorize('publish-posts'); // checks user's roles
// Adding 'moderator' role with 'publish-posts' permission
// is a database change — no code changes needed
}
public function delete(Post $post) {
$this->authorize('delete-posts');
}
}