Skip to content

RBAC — Role-Based Access Control

OWNER: aimee ALSO_USED_BY: anna (implementation), hugo (auth enforcement), faye, sytske

Access control is scoped per-tenant. A user can be an Admin in Org A and a Viewer in Org B. Roles and permissions are always evaluated within the context of the current tenant.


Access Control Model Selection

SCOPE_ITEM: access_control_model

RBAC (Role-Based Access Control) — Default

INCLUDES: Users are assigned roles within an organization INCLUDES: Roles have a fixed set of permissions INCLUDES: Permission check: does user's role in this org allow this action on this resource? INCLUDES: Simple to understand, implement, and audit

When to use: 80% of B2B SaaS projects. Start here.

ABAC (Attribute-Based Access Control)

INCLUDES: Access decisions based on attributes (user attributes, resource attributes, environment) INCLUDES: Policy engine evaluates rules like: IF user.department = "finance" AND resource.type = "invoice" THEN allow INCLUDES: Fine-grained, field-level access control possible

When to use: Regulated industries, complex data sensitivity requirements, field-level permissions. CHECK: ABAC adds 60-100 hours — confirm requirement before including

ReBAC (Relationship-Based Access Control)

INCLUDES: Access based on relationships between entities (user → team → project → document) INCLUDES: Graph-based permission evaluation INCLUDES: "Can user X access document Y?" resolved by traversing the relationship graph INCLUDES: Inspired by Google Zanzibar (used by Google Drive, Notion, etc.)

When to use: Collaborative products with nested sharing, document hierarchies, team structures. CHECK: ReBAC adds 100-150 hours — only for products where sharing/collaboration is core CHECK: Consider OpenFGA or SpiceDB as external policy engines if ReBAC needed

Decision Matrix

IF: Simple team product with 3-5 predefined roles THEN: RBAC (default) IF: Product has field-level visibility requirements or conditional access rules THEN: RBAC + ABAC layer IF: Product has nested sharing, folder hierarchies, or Google-Drive-like permissions THEN: RBAC + ReBAC (or full ReBAC) IF: Client says "we need custom permissions for everything" THEN: Challenge scope — usually RBAC with custom roles is sufficient


Standard Role Hierarchy

SCOPE_ITEM: default_roles

Role Description Typical Permissions
Owner Org creator, cannot be removed All permissions, transfer ownership, delete org
Admin Full org management Manage users, roles, billing, settings, all data
Member Standard user CRUD on assigned resources, view shared resources
Viewer Read-only access View resources, export data, no create/edit/delete
Billing Billing-only access Manage plan, payment methods, invoices. No data access
Guest Limited external access View specific shared resources only

CHECK: Confirm role names with client — some prefer "Manager" instead of "Admin" CHECK: Owner role must have at least one user at all times — prevent orphaned orgs CHECK: Billing role is separate from Admin — finance team should not need data access


Permission System Design

SCOPE_ITEM: permission_system

Permission Structure

INCLUDES: Permission format: resource:action (e.g., projects:create, users:delete)
INCLUDES: Resources: users, projects, documents, invoices, settings, billing, integrations, audit_log
INCLUDES: Actions: create, read, update, delete, export, manage, invite
INCLUDES: Wildcard: resource:* (all actions on resource), *:* (super admin)

Permission Matrix (Example)

Permission Owner Admin Member Viewer Billing
users:invite yes yes no no no
users:manage yes yes no no no
projects:create yes yes yes no no
projects:read yes yes yes yes no
projects:update yes yes own no no
projects:delete yes yes no no no
billing:manage yes yes no no yes
settings:manage yes yes no no no
audit_log:read yes yes no no no
audit_log:export yes no no no no

INCLUDES: "own" means user can only update resources they created CHECK: Generate permission matrix during scoping — client signs off


Custom Roles

SCOPE_ITEM: custom_roles INCLUDES: Org admin can create custom roles INCLUDES: Custom role = name + description + set of permissions (cherry-picked) INCLUDES: Custom roles scoped to organization (not global) INCLUDES: Maximum 20 custom roles per org (prevent permission sprawl) INCLUDES: Custom role assignment same as predefined roles OPTIONAL: Role templates (start from predefined role, modify permissions) CHECK: Custom roles add 30-50 hours of implementation CHECK: UI for permission selection must be clear — group permissions by resource


Organization Hierarchy

SCOPE_ITEM: org_hierarchy INCLUDES: Flat structure (single level) — default for most B2B SaaS OPTIONAL: Two-level hierarchy (org → teams/departments) OPTIONAL: Multi-level hierarchy (org → division → department → team) IF: Multi-level hierarchy needed THEN: Budget 60-100 extra hours, use RBAC with inheritance CHECK: Hierarchy adds complexity to every permission check — start flat CHECK: Most "hierarchy" needs are solved by teams (flat groups within org)

Team-Scoped Permissions

IF: Product has team concept THEN: Implement two-layer RBAC

INCLUDES: Org-level role (Admin, Member, Viewer) — controls org-wide actions
INCLUDES: Team-level role (Team Lead, Team Member, Team Viewer) — controls team data
INCLUDES: Permission check: org role AND team role evaluated together
INCLUDES: Team leads can manage team members but not org settings

Implementation with Drizzle (GE Stack)

Database Schema

SCOPE_ITEM: rbac_schema

INCLUDES: Table `roles` — id, tenant_id, name, slug, description, is_custom, is_default, created_at
INCLUDES: Table `permissions` — id, resource, action, description
INCLUDES: Table `role_permissions` — role_id, permission_id (many-to-many)
INCLUDES: Table `user_org_roles` — user_id, tenant_id, role_id (user's role in an org)
INCLUDES: Predefined roles seeded on tenant creation (Owner, Admin, Member, Viewer, Billing)
INCLUDES: Predefined permissions seeded on migration (all resource:action pairs)

Middleware Enforcement

SCOPE_ITEM: rbac_middleware

INCLUDES: Hono middleware: requirePermission('projects:create')
INCLUDES: Middleware reads user's role from JWT claims (cached, not DB lookup per request)
INCLUDES: Role's permissions loaded from Redis cache (invalidated on role change)
INCLUDES: If permission denied, return 403 with error code (not 404)
INCLUDES: Permission check happens AFTER tenant context is established

API Route Pattern

SCOPE_ITEM: rbac_route_pattern
INCLUDES: Every route declares required permission
INCLUDES: Route: POST /api/projects → requirePermission('projects:create')
INCLUDES: Route: GET /api/projects → requirePermission('projects:read')
INCLUDES: Route: PATCH /api/projects/:id → requirePermission('projects:update')
INCLUDES: Route: DELETE /api/projects/:id → requirePermission('projects:delete')
INCLUDES: Bulk operations check permission once, not per item

CHECK: Never rely on frontend to hide buttons as access control — always enforce server-side CHECK: 403 responses should include error code, not reason (information disclosure risk)


API Key Management

SCOPE_ITEM: api_key_management INCLUDES: API keys scoped to organization INCLUDES: Key format: prefix_randomstring (e.g., ge_live_abc123def456) INCLUDES: Prefix indicates environment (live, test, sandbox) INCLUDES: Key hashed with SHA-256 before storage (never store plaintext) INCLUDES: Key displayed once at creation (full value), then only prefix shown INCLUDES: Key has associated permission scopes (subset of role permissions) INCLUDES: Key usage tracked (last used, total requests, created_by) INCLUDES: Key rotation: create new key, grace period (48h), old key expires INCLUDES: Maximum 10 API keys per org (prevent key sprawl) COMPLIANCE: API key creation and deletion must be audit-logged CHECK: API keys are NOT user tokens — they represent the org, not a person CHECK: Rate limiting applies per API key, not per user

Scope-Based API Key Permissions

INCLUDES: Key scopes defined at creation: ["projects:read", "projects:create"]
INCLUDES: Scope validated on every API request using the key
INCLUDES: Narrower scope than the creating user's role is allowed
INCLUDES: Wider scope than the creating user's role is rejected

Personal Access Tokens

SCOPE_ITEM: personal_access_tokens INCLUDES: Per-user tokens (represent the user, not the org) INCLUDES: Inherit the user's current role permissions INCLUDES: If user's role changes, token permissions change too INCLUDES: Expiry: user-defined, max 1 year INCLUDES: Revocable by user or org admin OPTIONAL: Only if client's customers are developers (API-heavy product) CHECK: PATs simplify CLI and automation use cases — include if product has API focus


Platform Admin RBAC

SCOPE_ITEM: platform_admin_rbac INCLUDES: Separate from org-level RBAC — platform admins are the client's own staff INCLUDES: Roles: Super Admin, Support Agent, Billing Manager, Read-Only Auditor INCLUDES: Super Admin: all operations, impersonation, feature flags, system config INCLUDES: Support Agent: view org data, impersonate users, no billing or config changes INCLUDES: Billing Manager: manage plans, credits, invoices, no data access INCLUDES: Read-Only Auditor: view everything, change nothing INCLUDES: All platform admin actions written to separate audit log COMPLIANCE: Platform admin impersonation must be time-limited (max 1 hour) and audit-logged CHECK: Platform admin panel is accessed at /admin, not /settings — separate route group


Permission Caching Strategy

SCOPE_ITEM: permission_caching INCLUDES: User's effective permissions cached in Redis on login INCLUDES: Cache key: tenant:{tenant_id}:user:{user_id}:permissions INCLUDES: Cache TTL: 5 minutes INCLUDES: Cache invalidated on: role change, custom role permission change, user deactivation INCLUDES: JWT contains role slug (not full permissions) — permissions resolved from cache CHECK: Never put full permission list in JWT — token size bloats, stale permissions risk CHECK: Cache miss falls back to database lookup (not denied)


Testing RBAC

SCOPE_ITEM: rbac_testing INCLUDES: Test: User with Member role cannot access Admin-only endpoints INCLUDES: Test: User with Viewer role cannot create, update, or delete resources INCLUDES: Test: Custom role with specific permissions grants exactly those permissions INCLUDES: Test: Role change takes effect within 5 minutes (cache invalidation) INCLUDES: Test: API key with limited scopes cannot exceed those scopes INCLUDES: Test: Deactivated user's sessions and API keys are immediately revoked INCLUDES: Test: Owner role cannot be removed from last remaining owner CHECK: RBAC tests run in CI on every deployment — permission bypass is severity-1