Lesson 2 / 32
Tenants, Users and Memberships
Model tenants, users and roles correctly so one person can belong to several tenants, and avoid the classic mistake of hanging data off the user.
By Rahul Agarwal · 15 years in web development
Users live inside tenants (usually)
A user is a person who logs in; a tenant is the organisation that owns data. The relationship between them is often many-to-many: a chartered accountant may work in the accounts of several client companies, and a freelancer may join many workspaces. So most B2B SaaS products keep three things: a tenants table, a users table (one identity per person) and a membership table holding (tenant_id, user_id, role). Business data such as invoices or projects carries a tenant_id, not a user_id, because the company owns it even after an employee leaves. When a user signs in, the app either asks which tenant to open or picks a default, and from then on every request runs as that user inside that tenant. Some consumer products are simpler: each user is their own tenant. Decide this early, because changing it later means rewriting authorisation and data ownership.
Modelling memberships
One person, two tenants, a different role in each. Roles belong to the membership, not to the user.
from dataclasses import dataclass, field
@dataclass
class Tenant:
id: str
name: str
plan: str
@dataclass
class User:
email: str
memberships: dict = field(default_factory=dict) # tenant_id -> role
acme = Tenant("t_acme", "Acme Pvt Ltd", "pro")
zen = Tenant("t_zen", "Zen Foods", "free")
priya = User("priya@example.com")
priya.memberships[acme.id] = "admin"
priya.memberships[zen.id] = "viewer" # one person, two tenants
for tenant_id, role in priya.memberships.items():
print(f"{priya.email} is {role} in {tenant_id}")
Output:
priya@example.com is admin in t_acme priya@example.com is viewer in t_zen
Data belongs to the tenant
A frequent design bug is projects.owner_user_id as the only ownership column. When that employee leaves and their account is deleted, the company's projects become orphans. Store tenant_id on every business row, and keep created_by only as an audit fact.
Quick check: In a B2B SaaS, which column should decide who owns an invoice row?
- The id of the user who created it
- The id of the user currently logged in
- The email domain of the creator
- The tenant_id of the organisation it belongs to
Answer
The tenant_id of the organisation it belongs to — The organisation owns business data. Users come and go, and email domains are not reliable identifiers, so ownership and isolation are keyed on tenant_id.