Access Control System Low-Level Design

What is Access Control?

Access control determines which users can perform which actions on which resources. Three major models: ACL (Access Control Lists), RBAC (Role-Based Access Control), and ABAC (Attribute-Based Access Control). Google Drive, AWS IAM, and GitHub all implement variants of these. The design challenge: fast permission checks (every API request needs one), flexible enough to model complex sharing rules, and maintainable as permissions evolve.

RBAC: Role-Based Access Control

Users are assigned roles; roles have permissions; permissions define allowed actions on resource types.

Role(role_id UUID, name VARCHAR, description VARCHAR, tenant_id UUID)
-- e.g.: 'admin', 'editor', 'viewer', 'billing_admin'

Permission(permission_id UUID, role_id UUID,
           action VARCHAR,         -- 'document:read', 'document:write', 'user:delete'
           resource_type VARCHAR,  -- 'document', 'user', 'billing'
           resource_id UUID)       -- NULL = applies to all resources of that type

UserRole(user_id UUID, role_id UUID, granted_by UUID, granted_at TIMESTAMP,
         expires_at TIMESTAMP)  -- optional time-bounded roles

Check: does user X have action ‘document:write’ on document Y?

def can(user_id, action, resource_type, resource_id=None):
    roles = get_user_roles(user_id)   # cached in Redis
    for role in roles:
        perms = get_role_permissions(role.id)  # cached
        for perm in perms:
            if perm.action == action and perm.resource_type == resource_type:
                if perm.resource_id is None or perm.resource_id == resource_id:
                    return True
    return False

Resource-Level Sharing (Google Drive model)

For user-generated content, users share specific items with specific people:

ResourcePermission(share_id UUID,
                   resource_type VARCHAR,
                   resource_id UUID,
                   grantee_type ENUM(USER, GROUP, ANYONE),
                   grantee_id UUID,    -- NULL if grantee_type=ANYONE
                   role ENUM(VIEWER, COMMENTER, EDITOR, OWNER),
                   created_by UUID, created_at TIMESTAMP)

-- Can user X view document Y?
SELECT COUNT(*) FROM ResourcePermission
WHERE resource_type='document' AND resource_id=:doc_id
  AND grantee_type IN ('USER', 'ANYONE')
  AND (grantee_id = :user_id OR grantee_type = 'ANYONE')
  AND role IN ('VIEWER', 'COMMENTER', 'EDITOR', 'OWNER')

Hierarchical Permissions (Inheritance)

In Google Drive, sharing a folder shares all contents. Implementation:

-- Resource hierarchy
ResourceParent(resource_id UUID, parent_id UUID)
-- document → folder → shared drive

-- Permission check with inheritance:
-- 1. Check direct permission on resource
-- 2. Walk up the parent chain, check permission at each ancestor
-- 3. First match wins

def can_access(user_id, resource_id, action):
    path = get_ancestor_path(resource_id)  # [resource_id, folder_id, drive_id]
    for rid in path:
        perm = get_direct_permission(user_id, rid)
        if perm:
            return has_action(perm.role, action)
    return False  # no permission found in hierarchy

Caching Strategy

Permission checks happen on every API request. Cache aggressively:

# Cache user's roles: TTL=5min
redis.setex(f'user_roles:{user_id}', 300, json.dumps(roles))

# Cache role permissions: TTL=1h (roles change rarely)
redis.setex(f'role_perms:{role_id}', 3600, json.dumps(perms))

# Cache resource permissions: TTL=1min (sharing changes more often)
redis.setex(f'resource_perms:{resource_id}', 60, json.dumps(perms))

# On permission change: invalidate relevant cache keys
def grant_access(user_id, resource_id, role):
    db.insert(ResourcePermission(...))
    redis.delete(f'resource_perms:{resource_id}')
    redis.delete(f'user_roles:{user_id}')

Google Zanzibar-Inspired Design

For large-scale permission systems (Google Drive, GitHub), Google’s Zanzibar paper describes a permission store based on relation tuples: (object, relation, user). For example: (doc:123, viewer, user:456). Permission check: “can user:456 view doc:123?” expands to checking if the tuple exists, plus following group memberships and parent-folder inheritance. Zanzibar uses consistent snapshots to prevent new enemy problems (seeing new permissions before old ones are fully propagated).

Key Design Decisions

  • RBAC for system-wide roles, ResourcePermission for user-controlled sharing — different access models for different use cases
  • Redis cache for role/permission lookups — every API request needs a permission check; DB queries would be too slow
  • Invalidate on change, not update — simpler than cache coherence; accept brief inconsistency after permission changes
  • Separate grantee_type (USER/GROUP/ANYONE) — supports public sharing without a row per user
  • expires_at on UserRole — time-bounded access for contractors, temporary grants

{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What is the difference between RBAC and ABAC?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”RBAC (Role-Based Access Control) assigns users to roles; roles have predefined permissions on resource types. Simple to implement and audit — "admins can edit documents." ABAC (Attribute-Based Access Control) evaluates policies against attributes of the user, resource, and environment: "users in the finance department can view budget documents during business hours from the corporate network." ABAC is more expressive but harder to audit and reason about. Most SaaS products use RBAC; government and enterprise security systems often need ABAC.”}},{“@type”:”Question”,”name”:”How does Google Drive-style sharing work at the database level?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Each shared item has rows in a ResourcePermission table: (resource_type, resource_id, grantee_type, grantee_id, role). grantee_type can be USER (direct share), GROUP (team share), or ANYONE (public link). To check if user X can edit document Y: query ResourcePermission WHERE resource_id=Y AND (grantee_id=X OR grantee_type=ANYONE) AND role IN (EDITOR, OWNER). For folder inheritance: walk up the parent hierarchy and check each ancestor for a matching permission.”}},{“@type”:”Question”,”name”:”How do you cache permission checks efficiently?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Cache at two levels: user roles (TTL=5min, invalidate on role assignment change) and role permissions (TTL=1h, invalidate when role permissions are edited). For resource-level sharing, cache the permission list per resource (TTL=1min). On any permission change: delete the affected cache keys — never update them directly. Cache-aside with delete ensures you never serve a stale permission after a revocation, just at the cost of one cache miss per change.”}},{“@type”:”Question”,”name”:”How does Google Zanzibar work for permission at scale?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Zanzibar stores permissions as relation tuples: (object, relation, user). Example: (doc:123#viewer, user:456) means user 456 is a viewer of doc 123. (folder:789#parent, doc:123) enables inheritance — viewer of folder 789 is also viewer of doc 123. Permission check "can user:456 view doc:123?" expands the relation graph recursively. Zanzibar handles billions of checks per second by sharding the relation store, caching heavily, and using consistent snapshots to prevent race conditions during permission changes.”}},{“@type”:”Question”,”name”:”How do you implement time-bounded access grants?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Add an expires_at column to the UserRole or ResourcePermission table. During permission checks, add the condition: expires_at IS NULL OR expires_at > NOW(). A background job periodically removes expired grants or marks them inactive. Common use cases: contractor access for 90 days, temporary elevated privileges for an on-call engineer, time-limited sharing link that expires after 7 days. The expires_at approach is simpler than revoking and re-granting because the grant record serves as an audit trail.”}}]}

Access control and permission system design is commonly asked in Google system design interview guide.

RBAC and resource-level permission systems are discussed in Atlassian system design interview questions.

Access control and IAM-style permission design is covered in Amazon system design interview preparation.

Scroll to Top