Capabilities/SaaS Platform

Role-Based Access Control

Permission models, policy engines, and row-level security for multi-user applications

RBACpermissionsauthorizationsecuritypolicy

Learning lens

Start with judgment, not a universal prescription.

This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.

Evidence

Conceptual pattern

0->1 default

Start with clear tenant boundaries, boring data ownership, and reversible choices before optimizing for platform scale.

Cost drivers

tenant isolationcustom configurationbackground jobssupport tooling

Watch for breakpoints

  • -large tenants affect small tenants
  • -support teams need self-serve controls
  • -migration work becomes tenant-specific

Learn first

Concepts behind this architecture

The Problem

As applications grow beyond a single user, you need to control who can do what. A junior developer shouldn’t delete production databases. A team member shouldn’t see billing details. A viewer shouldn’t edit documents. Role-Based Access Control (RBAC) assigns permissions to roles, and roles to users — but production systems need more: hierarchical roles, resource-level permissions, attribute-based policies, and real-time permission changes. The authorization check must be fast (sub-millisecond), consistent, and auditable, sitting on every request’s critical path.

Architectural Approaches

Simple RBAC (Role → Permissions)

Define a fixed set of roles (admin, editor, viewer), each with a set of permissions (create, read, update, delete). Assign roles to users. On each request, check if the user’s role includes the required permission. Implemented with a roles table and a role_permissions mapping.

Pros

  • +Simple to understand, implement, and audit
  • +Works well for applications with clear role hierarchies
  • +Fast evaluation — single lookup per request
  • +Easy to explain permission model to end users

Cons

  • −Role explosion — too many roles for fine-grained needs
  • −No resource-level permissions (can edit ALL documents, not specific ones)
  • −Adding a new permission requires updating role definitions
  • −Doesn't handle complex conditions (time-based, attribute-based)

Relationship-Based Access Control (ReBAC)

Permissions are derived from relationships between users and resources. “User X can edit Document Y because X is a member of Team Z which owns Folder W which contains Document Y.” Models the permission graph as a series of relationships (tuples). Used by Google Zanzibar and its open-source derivatives (SpiceDB, OpenFGA).

Pros

  • +Naturally handles resource-level permissions
  • +Supports complex hierarchies (org → team → project → document)
  • +Permission inheritance through relationship chains
  • +Scales to billions of relationships with proper infrastructure

Cons

  • −More complex to implement and reason about
  • −Relationship graph traversal can be slow without caching
  • −Debugging "why can/can't user X do Y?" is harder
  • −Requires a dedicated authorization service or infrastructure

Attribute-Based Access Control (ABAC)

Permissions are evaluated based on attributes of the user, resource, action, and environment. Policies are written as rules: “Allow if user.department == resource.department AND action == ‘read’ AND time.hour >= 9 AND time.hour <= 17.” Most flexible but most complex.

Pros

  • +Extremely flexible — any attribute can be a policy input
  • +No role explosion — policies adapt to any dimension
  • +Supports contextual conditions (time, location, IP)
  • +Can model RBAC as a subset (role is just an attribute)

Cons

  • −Policy complexity can become unmanageable
  • −Slower evaluation — must fetch attributes at decision time
  • −Harder to audit ("who has access to what?" is a graph problem)
  • −Requires a policy language (Rego, Cedar, Casbin)

Architecture

Loading diagram...
Role-Based Access Control — High-level architecture

Key Components

Policy Engine

Evaluates authorization policies against the request context (user, resource, action). Supports RBAC role checks, ReBAC relationship traversal, or ABAC attribute evaluation depending on the chosen model. Must return a decision (allow/deny) in under 1ms.

Role & Permission Store

Database tables storing roles, permissions, role-permission mappings, and user-role assignments. In ReBAC, stores relationship tuples (user, relation, object). Supports tenant-scoped roles for multi-tenant applications.

Permission Cache

Redis-based cache of evaluated permissions. Caches user→role mappings and frequently checked permissions. Invalidated on role changes. TTL-based expiry ensures eventual consistency (typically 5-60 second TTL).

Authorization Middleware

Intercepts every API request, extracts the user identity from the JWT, determines the required permission for the endpoint, and calls the policy engine. Returns 403 Forbidden if the check fails. Must be applied consistently across all routes.

Admin Dashboard

UI for managing roles, permissions, and assignments. Supports creating custom roles, viewing effective permissions for a user, and simulating permission checks (“what would happen if user X tried action Y on resource Z?”).

Audit Logger

Records every authorization decision (who, what, when, allowed/denied) for compliance and debugging. Enables answering “who accessed this resource?” and “why was this user denied access?” questions.

Data Model

Loading diagram...
Role-Based Access Control — Entity relationship diagram

Scoped Roles and Resource-Level Permissions

Basic RBAC assigns global roles — “User X is an admin.” But most apps need scoped roles — “User X is an admin OF project Y but only a viewer OF project Z.”

Implementation: Add scope fields to the user-role assignment:

  • scope_type: “organization”, “project”, “team”, “document”
  • scope_id: the specific resource ID

Permission check flow:

  1. User requests to edit Project Y
  2. Look up user’s roles scoped to Project Y
  3. Also look up roles scoped to Project Y’s parent (Organization Z) — role inheritance
  4. Check if any of those roles include the “project:edit” permission
  5. Allow or deny

Hierarchy: Permissions inherit downward. An org admin is implicitly a project admin for all projects in the org. Implement by walking up the resource hierarchy during permission checks. Cache the effective permissions per (user, scope) pair.

Performance: With scoped roles, the permission check involves multiple lookups. Pre-compute and cache the effective permission set per user-scope combination. Invalidate cache on role changes (pub/sub notification from the role service).

Google Zanzibar and ReBAC

Google’s Zanzibar paper (2019) introduced a scalable authorization system based on relationship tuples.

Core concept: Authorization data is stored as tuples: (object, relation, user). For example:

  • (doc:readme, owner, user:alice) — Alice owns the readme doc
  • (folder:eng, viewer, team:engineering#member) — Engineering team members can view the eng folder
  • (doc:readme, parent, folder:eng) — readme doc is in the eng folder

Permission check: “Can Alice view doc:readme?” The system checks:

  1. Is Alice a direct viewer of doc:readme? → No
  2. Does doc:readme have a parent? → Yes, folder:eng
  3. Is Alice a viewer of folder:eng? → Check if Alice is a member of team:engineering → Yes!
  4. Permission granted via relationship chain.

Open-source implementations:

  • SpiceDB (AuthZed) — Go-based, gRPC API, closest to Zanzibar
  • OpenFGA (Auth0/Okta) — supports multiple storage backends
  • Permify — Kubernetes-native, schema-first approach

ReBAC is ideal when your permission model mirrors your data hierarchy (organizations → projects → resources).

Performance and Caching Strategy

Authorization sits on the hot path of every request. It must be fast.

Caching layers:

  1. In-process cache (LRU, 1000 entries, 30s TTL): Fastest. Caches (user_id, permission, scope) → boolean. Hit rate: 60-80% for typical web apps.
  2. Redis cache (60s TTL): Shared across instances. Caches user’s effective role set per scope. Hit rate: 90%+.
  3. Database (source of truth): Only hit on cache miss. Must be fast — proper indexes on user_id, role_id, scope columns.

Cache invalidation: When a role assignment changes:

  1. Invalidate the user’s cache entries (in-process + Redis)
  2. Publish an event so all instances invalidate their local cache
  3. For security-critical changes (role revocation), force re-evaluation by bumping a version counter

Batch prefetching: For UI pages that show multiple resources, prefetch permissions for all visible resources in one batch call. “Can user X do [view, edit, delete] on resources [A, B, C, D]?” — single round-trip returning a permission matrix.

Latency targets: In-process cache hit: <0.01ms. Redis cache hit: <1ms. Database lookup: <5ms. Any authorization check >10ms indicates a problem.

Real-World Examples

Google Workspace

Uses Zanzibar for authorization across Drive, Docs, Calendar, and more. Handles trillions of authorization checks per day. Relationship tuples model sharing (viewer, editor, owner) with inheritance through folders and organizational units.

GitHub

Scoped RBAC with organization → team → repository hierarchy. Roles (admin, maintain, write, triage, read) are scoped per repository. Team-level permissions inherit to repositories. Custom repository roles available for Enterprise.

AWS IAM

ABAC policy engine evaluating JSON policies against request context. Policies attached to users, groups, and roles. Supports conditions (IP range, time, MFA), resource-level permissions, and cross-account access via trust policies.

Notion

ReBAC model where permissions flow through the page hierarchy (workspace → teamspace → page → subpage). Supports sharing individual pages with specific users or groups, overriding inherited permissions.

Architectural Tradeoffs

RBAC vs ReBAC vs ABAC

Pros

  • +RBAC: simplest to implement and reason about
  • +ReBAC: natural for hierarchical data, resource-level permissions
  • +ABAC: most flexible, handles any authorization pattern

Cons

  • −RBAC: role explosion for fine-grained needs
  • −ReBAC: requires dedicated infrastructure (SpiceDB, OpenFGA)
  • −ABAC: policy complexity can become unmanageable

Embedded authorization vs external service

Pros

  • +Embedded: lowest latency, simplest deployment
  • +External: centralized policy management across microservices
  • +Embedded: no additional infrastructure dependency

Cons

  • −Embedded: duplicated logic across services, harder to audit
  • −External: adds network hop, becomes a critical dependency
  • −External: must handle service unavailability (fail open vs closed)

Fail-open vs fail-closed on authorization service outage

Pros

  • +Fail-open: application stays available during auth service outage
  • +Fail-closed: no unauthorized access is ever possible
  • +Cached decisions provide a middle ground

Cons

  • −Fail-open: security risk during outages
  • −Fail-closed: application becomes unavailable if auth service is down
  • −Cached decisions may be stale (revoked permissions still cached)

Learn Role-Based Access Control

Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.

Explore More Capabilities