# System Design: Access Control and Authorization — RBAC, ABAC, and Policy Enforcement (2025)

Source: https://www.techinterview.org/post/3233466288/system-design-access-control/
Updated: 2026-04-17 · techinterview.org

## Authorization Models

**ACL (Access Control List)**: per-resource list of who can do what. Simple but does not scale — updating access for a role change requires touching every resource. **RBAC (Role-Based Access Control)**: users are assigned roles; roles have permissions; permissions are (resource, action) pairs. Scalable for enterprise: changing a role's permissions updates access for all users with that role. **ABAC (Attribute-Based Access Control)**: policies evaluate attributes of the subject (user department, clearance), resource (classification, owner), action, and environment (time of day, IP range). Most expressive but more complex to implement and audit. **ReBAC (Relationship-Based Access Control)**: Google Zanzibar model — permissions are derived from relationships in a graph. "User A can view document D if A is a viewer of D, or A is a member of a group that is a viewer of D."

## RBAC Data Model


```
-- Core RBAC tables
CREATE TABLE users (user_id UUID PRIMARY KEY, email TEXT, ...);
CREATE TABLE roles (role_id UUID PRIMARY KEY, name TEXT, description TEXT);
CREATE TABLE permissions (
    perm_id UUID PRIMARY KEY,
    resource_type TEXT,   -- "document", "project", "billing"
    resource_id UUID,     -- NULL = applies to all resources of this type
    action TEXT           -- "read", "write", "delete", "admin"
);
CREATE TABLE role_permissions (role_id UUID, perm_id UUID, PRIMARY KEY (role_id, perm_id));
CREATE TABLE user_roles (
    user_id UUID, role_id UUID,
    scope_type TEXT,      -- "global", "organization", "project"
    scope_id UUID,        -- NULL for global
    PRIMARY KEY (user_id, role_id, scope_type, COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::UUID))
);
```

Scoped roles: a user can be ADMIN of project A but VIEWER of project B. The scope_type and scope_id columns capture this. Authorization check: does user U have permission to perform action A on resource R of type T?


```
SELECT 1
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.perm_id = p.perm_id
WHERE ur.user_id = $user_id
  AND p.action = $action
  AND p.resource_type = $resource_type
  AND (p.resource_id IS NULL OR p.resource_id = $resource_id)
  AND (ur.scope_type = 'global'
       OR (ur.scope_type = $scope_type AND ur.scope_id = $scope_id))
LIMIT 1;
```


## Policy Decision Point (PDP) and Policy Enforcement Point (PEP)

The PDP evaluates authorization requests and returns ALLOW/DENY. The PEP intercepts requests at service boundaries and calls the PDP. Separation of concerns: services contain no authorization logic — they delegate to the PDP. Implementation choices: **Centralized PDP**: a dedicated authorization service (Open Policy Agent, AWS IAM, custom). Simple operationally but adds network latency to every authorization check. Cache at the PEP: store allow/deny decisions in a local cache with short TTL (< 60s) to avoid per-request network calls. **Embedded PDP**: the policy engine runs as a sidecar or in-process. Eliminates network latency; policy updates are pushed to all instances. Open Policy Agent (OPA) supports this with bundle distribution.

## Google Zanzibar-Style ReBAC

Zanzibar stores tuples: (object, relation, user). Example: (doc:readme, viewer, user:alice) or (doc:readme, viewer, group:eng#member). Authorization check: "Can user:alice view doc:readme?" resolves by checking all viewer tuples for doc:readme, recursively expanding group memberships. The result is a graph traversal. Scale challenges: the tuple store has billions of rows (Google handles trillions). Key optimizations: **Zookies (consistency tokens)**: each write returns a token encoding the write timestamp. Subsequent reads include the token and are guaranteed to see that write (solves the "new enemy" problem where a permission removal is not yet visible). **Namespace configs**: schema definitions for each relation type, enabling query optimization. **Leopard index**: precomputed group-membership expansions to avoid deep recursive traversals at query time. Open-source implementations: Ory Keto, SpiceDB, Authzed.

See also: [Atlassian Interview Prep](https://techinterview.org/atlassian/)

See also: [Databricks Interview Prep](https://techinterview.org/databricks/)

See also: [Stripe Interview Prep](https://techinterview.org/stripe/)
