Who should read this
Summary: In B2B SaaS, permissions are the area most often deferred with “we will handle it later” — and the most painful to refactor. The safest path is to design around three axes from day one (tenant, role, resource), start with RBAC, and extend to ReBAC only when the need is proven.
This article is written for backend developers designing multi-tenant SaaS systems.
Comparing three permission models
| RBAC | ABAC | ReBAC | |
|---|---|---|---|
| Core concept | User -> Role -> Permission | Attribute (context) based policies | Relationship (graph) based |
| Best fit | 10 or fewer roles | Complex attribute combinations | Resource ownership is central |
| Implementation difficulty | Low (3 DB tables) | Medium (policy engine) | High (relationship graph) |
| Performance | O(1) lookup | Policy evaluation cost | Graph traversal cost |
| Prominent tools | Build your own | OPA, Cedar | OpenFGA, Oso, SpiceDB |
| Example | Admin, Editor, Viewer | "dept=Marketing AND time=business hours" | "user owns doc via folder" |
Starting with RBAC — three DB tables
-- Minimal multi-tenant RBAC schema
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
plan TEXT DEFAULT 'free'
);
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
org_id UUID REFERENCES organizations(id),
name TEXT NOT NULL, -- 'owner', 'admin', 'editor', 'viewer'
permissions JSONB NOT NULL DEFAULT '[]'
);
CREATE TABLE org_members (
user_id UUID NOT NULL,
org_id UUID REFERENCES organizations(id),
role_id INT REFERENCES roles(id),
PRIMARY KEY (user_id, org_id)
); The cardinal rule: every query must include an org_id filter. Fetching data without WHERE org_id = $current_org is a cross-tenant data leak.
Defense in depth — three layers of permission checks
- API middleware — Verify role when the request arrives (first line of defense)
- DB query — Row Level Security or mandatory
WHERE org_id = ?(second line of defense) - UI rendering — Hide buttons and menus the user lacks permission for (UX, not security)
Using Supabase Row Level Security (RLS) makes the second line of defense automatic at the database level.
When to go beyond RBAC
- Resource ownership matters: “Only the creator of this document can delete it” — move to ReBAC (relationship-based)
- Complex attribute combinations: “Marketing team + business hours + specific region” — move to ABAC (attribute-based)
- Role explosion (20+ roles): Consider ABAC or ReBAC to tame the complexity
Most B2B SaaS products get by for 3—5 years with RBAC plus a single “resource owner” check.
Pitfalls to avoid
Further reading
- Modular Monolith vs Microservices: 2026 Architecture Decision Guide — The architecture your permission system sits on
- REST vs GraphQL vs tRPC: 2026 Decision Guide — The API layer where permission checks live
- Supabase vs PlanetScale vs Neon: Choosing Postgres for SaaS — Databases that support RLS