Access Control Knowledge Base
Quick reference for access control models, authorization patterns, and PHP implementations.
Access Control Models Comparison
| Model | Full Name | Basis | Granularity | Scalability | Complexity |
|---|
| ACL | Access Control List | Per-resource permissions | Fine | Poor at scale | Low |
| RBAC | Role-Based Access Control | Roles assigned to users | Medium | Good | Medium |
| ABAC | Attribute-Based Access Control | Policies over attributes | Very fine | Excellent | High |
| ReBAC | Relationship-Based Access Control | Object relationships | Very fine | Excellent | High |
Decision Matrix: When to Use Which Model
| Scenario | Recommended | Why |
|---|
| Simple app, few resources | ACL | Direct, easy to implement |
| Enterprise, department-based access | RBAC | Maps to organizational roles |
| Complex policies, dynamic rules | ABAC | Flexible attribute evaluation |
| Social graphs, shared resources | ReBAC | Natural relationship modeling |
| Multi-tenant SaaS | RBAC + tenant scope | Roles per tenant |
| Healthcare, finance (compliance) | ABAC | Fine-grained audit trail |
| Document sharing (Google Docs style) | ReBAC | Owner/editor/viewer relations |
| Microservices with JWT | RBAC (claims) | Stateless, token-based |
RBAC: Role-Based Access Control
Role Hierarchy
┌─────────────────────────────────────────────────────────────────────────────┐
│ RBAC ROLE HIERARCHY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ SUPER_ADMIN │ │
│ │ (all perms) │ │
│ └──────┬───────┘ │
│ │ inherits │
│ ┌──────▼───────┐ │
│ │ ADMIN │ │
│ │ (manage) │ │
│ └──────┬───────┘ │
│ ┌──────────┼──────────┐ │
│ │ inherits │ │ inherits │
│ ┌──────▼───────┐ │ ┌───────▼──────┐ │
│ │ MANAGER │ │ │ EDITOR │ │
│ │ (approve) │ │ │ (create/edit)│ │
│ └──────┬───────┘ │ └───────┬──────┘ │
│ │ │ │ │
│ └──────────┼──────────┘ │
│ ┌──────▼───────┐ │
│ │ USER │ │
│ │ (read) │ │
│ └──────┬───────┘ │
│ ┌──────▼───────┐ │
│ │ GUEST │ │
│ │ (limited) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Permission Inheritance
| Role | Own Permissions | Inherited From | Total Permissions |
|---|
| GUEST | view_public | - | 1 |
| USER | view, create | GUEST | 3 |
| EDITOR | edit, publish | USER | 5 |
| MANAGER | approve, assign | USER | 5 |
| ADMIN | manage_users, configure | MANAGER, EDITOR | 10 |
| SUPER_ADMIN | all | ADMIN | all |
RBAC PHP Implementation
<?php
declare(strict_types=1);
namespace Domain\Authorization;
final readonly class Role
{
/**
* @param list<Permission> $permissions
* @param list<self> $parents
*/
public function __construct(
private string $name,
private array $permissions = [],
private array $parents = [],
) {}
public function hasPermission(Permission $permission): bool
{
if (in_array($permission, $this->permissions, true)) {
return true;
}
foreach ($this->parents as $parent) {
if ($parent->hasPermission($permission)) {
return true;
}
}
return false;
}
public function getName(): string
{
return $this->name;
}
/** @return list<Permission> */
public function getAllPermissions(): array
{
$permissions = $this->permissions;
foreach ($this->parents as $parent) {
$permissions = array_merge($permissions, $parent->getAllPermissions());
}
return array_values(array_unique($permissions));
}
}
ABAC: Attribute-Based Access Control
Core Concepts
| Concept | Description | Example |
|---|
| Subject | Who is requesting access | User with attributes (role, department, clearance) |
| Resource | What is being accessed | Document with attributes (classification, owner) |
| Action | Operation being performed | read, write, delete, approve |
| Environment | Contextual conditions | Time of day, IP range, MFA status |
Policy Evaluation
┌─────────────────────────────────────────────────────────────────────────────┐
│ ABAC POLICY EVALUATION │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Request(subject, resource, action, environment) │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Policy Decision │ │
│ │ Point (PDP) │ ◀── Policy Store (rules) │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────┼──────┐ │
│ ▼ ▼ ▼ │
│ Policy1 Policy2 PolicyN │
│ ALLOW DENY ALLOW │
│ │ │ │ │
│ └──────┼──────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Combining Algo │ │
│ │ (deny-overrides) │ │
│ └────────┬─────────┘ │
│ ▼ │
│ DENY │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
ABAC Policy Implementation
<?php
declare(strict_types=1);
namespace Domain\Authorization;
final readonly class AbacPolicy
{
public function __construct(
private string $name,
private string $description,
/** @var list<callable(AuthorizationContext): ?bool> */
private array $rules,
) {}
public function evaluate(AuthorizationContext $context): ?bool
{
foreach ($this->rules as $rule) {
$result = $rule($context);
if ($result === false) {
return false;
}
}
return true;
}
public function getName(): string
{
return $this->name;
}
}
final readonly class AuthorizationContext
{
public function __construct(
private array $subjectAttributes,
private array $resourceAttributes,
private string $action,
private array $environmentAttributes = [],
) {}
public function getSubjectAttribute(string $key): mixed
{
return $this->subjectAttributes[$key] ?? null;
}
public function getResourceAttribute(string $key): mixed
{
return $this->resourceAttributes[$key] ?? null;
}
public function getAction(): string
{
return $this->action;
}
public function getEnvironmentAttribute(string $key): mixed
{
return $this->environmentAttributes[$key] ?? null;
}
}
ReBAC: Relationship-Based Access Control
Google Zanzibar Model
Relationships are stored as tuples: user:relation:object
| Tuple | Meaning |
|---|
user:123#viewer@document:456 | User 123 is a viewer of document 456 |
group:eng#member@user:123 | User 123 is a member of group eng |
document:456#parent@folder:789 | Document 456 is in folder 789 |
folder:789#viewer@group:eng#member | Members of eng group are viewers of folder 789 |
Relationship Graph
┌─────────────────────────────────────────────────────────────────────────────┐
│ ReBAC RELATIONSHIP GRAPH │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ user:alice ──owner──▶ document:report │
│ │ │ │
│ │ member │ parent │
│ ▼ ▼ │
│ group:engineering folder:shared │
│ │ │ │
│ │ viewer │ viewer │
│ ▼ ▼ │
│ folder:eng-docs org:acme (all members can view) │
│ │
│ Check: Can alice view document:report? │
│ Path: alice ──owner──▶ document:report ✓ (owner implies viewer) │
│ │
│ Check: Can bob view folder:shared? │
│ Path: bob ──member──▶ org:acme ──viewer──▶ folder:shared ✓ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Multi-Tenancy Authorization
| Pattern | Description | Isolation Level |
|---|
| Tenant-scoped roles | User has different roles per tenant | Strong |
| Shared roles, tenant filter | Global roles, data filtered by tenant | Medium |
| Tenant in JWT claims | Tenant ID in authentication token | Medium |
| Row-level security | Database enforces tenant isolation | Strong |
| Separate schemas | Each tenant has own DB schema | Strongest |
Tenant-Scoped Authorization
<?php
declare(strict_types=1);
namespace Domain\Authorization;
final readonly class TenantPermissionChecker
{
public function __construct(
private TenantRoleRepositoryInterface $roleRepository,
) {}
public function hasPermission(string $userId, string $tenantId, Permission $permission): bool
{
$roles = $this->roleRepository->findRolesForUserInTenant($userId, $tenantId);
foreach ($roles as $role) {
if ($role->hasPermission($permission)) {
return true;
}
}
return false;
}
public function assertPermission(string $userId, string $tenantId, Permission $permission): void
{
if (!$this->hasPermission($userId, $tenantId, $permission)) {
throw new AccessDeniedException(
sprintf('User %s lacks %s in tenant %s', $userId, $permission->value, $tenantId),
);
}
}
}
Symfony Voter Implementation
<?php
declare(strict_types=1);
namespace Infrastructure\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
final class DocumentVoter extends Voter
{
public const string VIEW = 'DOCUMENT_VIEW';
public const string EDIT = 'DOCUMENT_EDIT';
public const string DELETE = 'DOCUMENT_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::VIEW, self::EDIT, self::DELETE], true)
&& $subject instanceof Document;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
/** @var Document $document */
$document = $subject;
return match ($attribute) {
self::VIEW => $this->canView($document, $user),
self::EDIT => $this->canEdit($document, $user),
self::DELETE => $this->canDelete($document, $user),
default => false,
};
}
private function canView(Document $document, User $user): bool
{
if ($document->isPublic()) {
return true;
}
return $document->getOwnerId() === $user->getId()
|| $user->hasRole('ROLE_ADMIN');
}
private function canEdit(Document $document, User $user): bool
{
return $document->getOwnerId() === $user->getId()
|| $user->hasRole('ROLE_EDITOR');
}
private function canDelete(Document $document, User $user): bool
{
return $document->getOwnerId() === $user->getId()
|| $user->hasRole('ROLE_ADMIN');
}
}
Laravel Gate/Policy
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Document;
use App\Models\User;
final class DocumentPolicy
{
public function view(User $user, Document $document): bool
{
if ($document->is_public) {
return true;
}
return $user->id === $document->owner_id
|| $user->hasRole('admin');
}
public function update(User $user, Document $document): bool
{
return $user->id === $document->owner_id
|| $user->hasRole('editor');
}
public function delete(User $user, Document $document): bool
{
return $user->id === $document->owner_id
|| $user->hasRole('admin');
}
}
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|
| Inline role checks | if ($user->role === 'admin') scattered in code | Use Voter/Policy abstraction |
| Hardcoded permissions | Permissions compiled into code | Store in database/config |
| Missing deny-by-default | Forgetting to deny when no rule matches | Default to DENY |
| Role explosion | Too many roles (one per use case) | Switch to ABAC or group permissions |
| Checking in views only | No server-side enforcement | Always check in backend |
| No audit logging | Cannot trace who accessed what | Log every authorization decision |
| Caching without invalidation | Stale permission grants | Event-driven cache invalidation |
| God role | Single admin role with all permissions | Granular admin sub-roles |
Common Violations Quick Reference
| Violation | Where to Look | Severity |
|---|
| No authorization on API endpoints | Controllers, routes | Critical |
| Hardcoded role names in business logic | Domain layer, services | Warning |
| Missing tenant isolation in queries | Repositories, query builders | Critical |
| No CSRF protection on state-changing forms | Form handlers, middleware | Critical |
| Permissions not cached | Voter/Policy implementations | Warning |
| No audit trail for access decisions | Authorization layer | Warning |
| Overly permissive default roles | Role configuration | Warning |
Detection Patterns
# Authorization implementations
Grep: "Voter|VoterInterface|AbstractVoter" --glob "**/*.php"
Grep: "Gate::define|Gate::allows|Gate::denies|@can" --glob "**/*.php"
Grep: "Policy|AuthorizesRequests" --glob "**/*.php"
# Role checks
Grep: "hasRole|isGranted|->can\(|->cannot\(" --glob "**/*.php"
Grep: "ROLE_|Permission::|PermissionEnum" --glob "**/*.php"
# Inline role checks (anti-pattern)
Grep: "role\s*===?\s*['\"]admin|role\s*===?\s*['\"]user" --glob "**/*.php"
# Tenant isolation
Grep: "tenant_id|tenantId|getTenantId" --glob "**/*.php"
Grep: "TenantScope|MultiTenant|BelongsToTenant" --glob "**/*.php"
# Casbin
Grep: "Enforcer|casbin|model\.conf|policy\.csv" --glob "**/*.php"
# Security configuration
Grep: "security\.yaml|security\.php|access_control" --glob "**/*.{yaml,php}"
Grep: "#\[IsGranted|@IsGranted|@Security" --glob "**/*.php"
# Missing authorization
Grep: "class.*Controller" --glob "**/*.php"
Grep: "class.*Action" --glob "**/Action/**/*.php"
References
For detailed information, load these reference files:
references/models.md — Detailed ACL, RBAC, ABAC, ReBAC analysis with scalability comparisons and PHP examples
references/php-implementations.md — Symfony Voters, Laravel Gates/Policies, Casbin PHP, permission caching strategies