All specs
infrastructurehigh complexity

Roles & Permissions

Used in 5 specs

Control who can do what inside your product — from simple role toggles to fine-grained, resource-scoped authorization.

Options

Authorization Model*

A fixed enum on the user record gates admin-only routes.
Users are assigned roles; roles bundle permissions; code checks permissions, not roles.
Permissions derived from attributes or graph relationships (owner, member, parent folder, etc.).

Permission Scope*

Permissions apply across the entire product.
A user has different roles in different workspaces or organizations.
Access lives on the resource itself — share a single document with specific users.

Custom Role Management

Roles (admin / member / viewer) are defined in code; customers cannot change them.
Admins can create roles and assign permissions.

Decision Points

Do users need different levels of access in different parts of the product?

If yes

Move beyond simple roles — at minimum, adopt RBAC.

If no

A two-value role enum on the user table is plenty.

Do users share individual items (documents, projects) with specific other users?

If yes

You need per-resource scope. Plan for ABAC or ReBAC now, not later.

If no

Workspace-level scope is usually sufficient.

Are enterprise customers asking to configure roles themselves?

If yes

Expose a role editor on the built-in permission primitives; price it.

If no

Ship a fixed role set and iterate based on feedback.

Do you need per-field permission granularity?

If yes

Move to ABAC or a policy engine (OpenFGA, Cerbos) — field-level rules are unmaintainable in RBAC.

If no

Row/resource-level checks are enough; keep the model coarse.

Do permissions need to inherit via hierarchical groups (folder → subfolder, org → team)?

If yes

ReBAC is the natural fit — Zanzibar-style graph traversal handles inheritance cleanly.

If no

Flat role-to-resource assignments are simpler and easier to debug.

Is deny-by-default the required posture?

If yes

Default every permission check to false; require an explicit grant. Standard for compliance-regulated products.

If no

Allow-by-default with blocklist rules is risky — only acceptable for internal tools.

Must users delegate access (X grants Y access to Z) without an admin?

If yes

Per-resource scope is required. Build a share action with grantor tracking in the audit log.

If no

Admin-mediated grants keep the authorization surface auditable and small.

Do you need policy-as-code (OPA, Cedar) managed alongside application code?

If yes

Adopt a policy engine — policies get versioned, reviewed, and tested like any source file.

If no

Keep authorization as a central module in app code until policy churn justifies the engine.

Do grants need to expire automatically (temporary access, contractor windows)?

If yes

Add an expires_at on every grant and a scheduled job that revokes on expiry. Critical for least-privilege compliance.

If no

Permanent grants with manual revocation are simpler but audit-unfriendly.

Do you need a break-glass / super-admin role for incident response?

If yes

Define it explicitly, require MFA to assume it, and audit-log every action taken under it. Keep the member list tiny.

If no

Regular admin + vendor support access covers most cases; avoid god-mode accounts.

Are sensitive role grants (e.g. billing-admin) subject to approval workflow?

If yes

Build a request + approve flow with a second approver on the granting side. Common in SOC 2 environments.

If no

Direct admin grants are faster — add audit logging instead.

Do enterprise customers need SCIM-driven group membership from their IdP?

If yes

Map SCIM groups to roles; treat the IdP as source of truth and avoid manual role edits for SCIM-managed users.

If no

In-app role management is simpler for SMB customers.

Do permission changes need a dedicated audit log (separate from general audit log)?

If yes

Emit a specialized authz-change stream — compliance reviewers need to query grants without sifting through all activity.

If no

Fold permission changes into the general audit log; tag them for easy filtering.

Do API keys need permissions separate from the user who minted them?

If yes

Give keys their own scoped permission set (typically a subset of the user's). Prevents accidental privilege inheritance.

If no

Mirror the minting user's permissions — simpler but revoking a user breaks their keys.

Can you push authorization into the database with row-level security?

If yes

Postgres RLS (or Supabase) centralizes enforcement at the data layer — out-of-band queries can't bypass it. Set auth context on every connection.

If no

Central policy module in app code is easier to debug and port across databases.

Are permission checks on your hot path (every list render)?

If yes

Cache lookups with a short TTL (30–60s) plus a revocation list checked per request — pure DB lookups will bottleneck.

If no

Uncached checks are fine; add caching only when profiler data demands it.

Tradeoffs

ComplexitySimple roles chosen

Fast to build but every 'special case' access rule becomes bespoke code that's hard to audit

ComplexityABAC / ReBAC chosen

Requires a policy engine and relationship store kept in sync with primary data

LatencyPer-resource scope enabled

Every list/read query must filter by ACL — expect query-plan work and caching investment

CostCustomer-defined roles enabled

Support load increases substantially — each customer now has a unique permission configuration

Go Deeper

Dependencies

user-system

Implementation Examples

OpenFGA

Open-source relationship-based authorization inspired by Google Zanzibar.

Oso

Authorization-as-a-service with a policy language (Polar) that compiles to database queries.

Cerbos

Self-hostable policy decision point with a declarative YAML policy language.

Used by specs

Slack-like Messaging AppCRM PlatformProject Management ToolSaaS StarterNote-taking / Knowledge Base