Skip to content
NORTH CRESTVENTURES
← Back to our work

SaaS Architecture · Tenant Workspace

One membership check on every tenant route

The tenant check returns the membership row, so the authorization and the context that authorises it are the same call.

Book a Demo

The problem

Multi-tenant backends fail quietly. The failure is rarely a missing permission check on the obvious endpoint — it is a new endpoint added later that forgets to ask, or a check that passes the right information to the wrong layer, or a token that carries a tenant claim and goes stale the moment a user's membership changes. The goal is one chokepoint that a future contributor cannot route around by accident.

The build

Tenancy is enforced by a single service method called at the top of every workspace-scoped route. It looks up the membership and, crucially, returns that row rather than a boolean — so the caller gets the role as authorization context in the same round trip instead of issuing a second query to find out who the user is. The access token carries only the user id and no tenant claim at all, which means tenant scoping is resolved from the database on every request and a token can never point at a workspace the user has since left. Role policy is a pure, dependency-free class with a deliberately asymmetric matrix: billing is closed to a plain member, while project writes are open to a member but closed to a billing role. A billing contact who can pay an invoice but cannot create projects is a specific decision, and encoding it as data rather than scattered conditionals keeps it legible. Refresh tokens are stored as SHA-256 hashes, rotate on every use, and enforce a real expiry. Rate limiting runs as ASGI middleware keyed by client and path, scoped to the auth routes, and the same middleware caps request bodies. Audit rows are written inline at the mutation site with an actor and a typed action.

  • require_member returns the membership row, merging the check and its context into one call
  • Access token carries no tenant claim, so scoping is re-resolved per request
  • Asymmetric role matrix: billing closed to members, project writes closed to billing

The outcome

Tenant membership is checked on every workspace route, and the isolation boundary is one readable method rather than a convention. Refresh rotation is single-use and expiry-checked, so a stolen token has a bounded window. Two frontends run against the same API — a Next.js dashboard and a dependency-free static UI served by FastAPI itself, so the backend is demonstrable with no Node toolchain. Stated plainly: this is a small build, the architecture document describes a ports-and-adapters boundary the code does not yet implement, and the isolation tests its docs claim are not written yet.

Next step

See the receptionist, or scope a build.

Book a demonstration of the AI receptionist, or tell us about a custom project. You get a real reply either way.