Permission Models Overview
Three main models:
- ACL (Access Control List): per-resource list of who can do what. Simple, fine-grained, but scales poorly (checking thousands of ACL entries per request).
- RBAC (Role-Based Access Control): users are assigned roles; roles have permissions. Scalable, audit-friendly. Works well for enterprise applications.
- ABAC (Attribute-Based Access Control): policies reference attributes of the user, resource, and environment (e.g., “managers can approve expenses < $1000 in their department”). Most flexible, most complex.
RBAC Data Model
User(user_id, email, name, status) Role(role_id, name, description, scope ENUM(GLOBAL,TENANT,RESOURCE)) Permission(permission_id, resource_type, action ENUM(READ,WRITE,DELETE,ADMIN)) RolePermission(role_id, permission_id) -- which permissions a role has UserRole(user_id, role_id, scope_id, granted_by, granted_at, expires_at) -- scope_id: NULL for global, tenant_id for tenant-scoped, resource_id for resource-scoped
Permission Check Algorithm
def can(user_id, action, resource_type, resource_id=None):
# Get all roles for user (global + tenant + resource-scoped)
roles = get_user_roles(user_id, resource_id)
# Get all permissions for those roles
permissions = get_permissions_for_roles(roles)
# Check if (resource_type, action) is in permissions
return (resource_type, action) in permissions
Cache at two levels: (1) User roles: Redis SET key=user_roles:{user_id}, TTL=5min. (2) Role permissions: Redis HASH key=role_perms:{role_id}, TTL=1hr. Invalidate on role assignment change or permission update. Avoid DB lookup on every request — permission checks happen on every API call.
Hierarchical Permissions
Resource hierarchy: Organization → Team → Project → Document. If a user has WRITE permission on a Team, they implicitly have WRITE on all Projects and Documents in that team (unless explicitly denied). Implementation: when checking if user can access Document D, also check permissions on D’s parent Project, its parent Team, and its parent Organization. Walk up the hierarchy until a grant is found. Cache the full permission set per (user, resource) with TTL=60s.
Row-Level Security (RLS)
Filter database queries based on the current user’s permissions. Instead of checking permission then fetching data, let the DB enforce access at query time:
-- PostgreSQL Row Level Security
CREATE POLICY user_documents ON documents
USING (owner_id = current_setting('app.current_user_id')::uuid
OR document_id IN (
SELECT resource_id FROM user_permissions
WHERE user_id = current_setting('app.current_user_id')::uuid
AND action = 'READ'
));
Set the current user ID before each query: SET LOCAL app.current_user_id = ‘{user_id}’. RLS prevents accidental data exposure from missing WHERE clauses in application code.
Permission Inheritance and Scope
Three role scopes:
- Global roles: apply across all resources (e.g., SUPER_ADMIN, BILLING_ADMIN)
- Tenant-scoped roles: apply within a specific tenant/organization (e.g., ORG_ADMIN for organization X)
- Resource-scoped roles: apply to a specific resource (e.g., DOCUMENT_EDITOR for document Y)
Permission resolution: global roles take precedence, then tenant-scoped, then resource-scoped. Deny overrides grant at the same level (explicit deny beats any grant).
Audit Trail
PermissionAudit(audit_id, user_id, action, resource_type, resource_id,
granted BOOL, reason, ip_address, created_at)
Log every permission check result (ALLOW/DENY) for security auditing. Store asynchronously (Kafka → audit DB) to avoid adding latency to API requests. Retain audit logs for 1-7 years depending on compliance requirements (HIPAA: 6 years, SOC2: 1 year).
Key Design Decisions
- RBAC for most enterprise apps — ABAC only if role explosion is unavoidable
- Cache user roles and role permissions to avoid per-request DB lookups
- RLS for defense in depth — DB-level enforcement catches application bugs
- Explicit deny takes precedence over grant to enable exception handling
- Scoped roles (global/tenant/resource) handle multi-tenant SaaS without separate permission tables per tenant
Atlassian products (Jira, Confluence) use complex permission systems. See common questions for Atlassian interview: permission and authorization system design.
Google system design covers permission systems and access control. Review design patterns for Google interview: permission system and RBAC design.
Databricks system design covers multi-tenant permissions and data access control. See patterns for Databricks interview: permission and authorization system design.
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering