# Permission and Authorization System Low-Level Design

Source: https://www.techinterview.org/post/3233467283/lld-permission-system/
Updated: 2026-04-18 · techinterview.org

## 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](https://www.techinterview.org/post/3233459919/atlassian-interview/).

Google system design covers permission systems and access control. Review design patterns for [Google interview: permission system and RBAC design](https://www.techinterview.org/post/3233460274/netflix-interview-guide-2026-streaming-architecture-recommendation-systems-and-engineering-excellence/).

Databricks system design covers multi-tenant permissions and data access control. See patterns for [Databricks interview: permission and authorization system design](https://www.techinterview.org/post/3233460280/databricks-interview-guide-2026-spark-internals-delta-lake-and-lakehouse-architecture/).

See also: [Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering](https://www.techinterview.org/post/3233460275/scale-ai-interview-guide-2026-data-infrastructure-rlhf-pipelines-and-ml-engineering/)
