OneShot

← All packs

Multi-Tenant Auth & RBAC Core

complex-universalverified

Orgs, invites, roles, sessions, and Postgres-RLS tenant isolation — the auth spine of a B2B SaaS, with cross-tenant isolation proven by the verify suite.

How install works

  • Accounts
  • Tools to install (exact versions this pack is pinned to)
  • A password you set yourself (read this before you deploy)

Time estimate: 15–20 minutes if you have none of this installed yet; 5 minutes if you already have Docker and an agent set up.

Full setup walkthrough ships in the zip after purchase.

Coming soon — $149
Execution receipt3 of 3 runs passed
Model
claude-sonnet-5
Run at
2026-08-02T08:56:49+00:00
Pack version
db6dd73df290
Tokens in / out
7,679,204 / 97,156 · per-run mean · billed input includes 7,230,557 cache reads, priced far below fresh input
Wall time
17.1 min · per-run mean
Deploy
not deployed

Acceptance checks

  • prerequisitesPASS
  • postgres-upPASS
  • postgres-readyPASS
  • deps-installPASS
  • migratePASS
  • migrate-idempotentPASS
  • buildPASS
  • server-startPASS
  • auth-signupPASS
  • auth-signup-duplicatePASS
  • auth-loginPASS
  • auth-mePASS
  • auth-logout-invalidates-server-sidePASS
  • auth-password-change-revokes-othersPASS
  • org-create-and-mePASS
  • invites-accept-flowPASS
  • invites-single-usePASS
  • members-visibilityPASS
  • invites-permissionsPASS
  • members-role-change-freshPASS
  • members-role-change-owner-onlyPASS
  • members-removePASS
  • members-last-owner-protectedPASS
  • invites-revokePASS
  • invites-reinvite-supersedesPASS
  • org-delete-owner-onlyPASS
  • iso-setupPASS
  • iso-http-note-scopingPASS
  • iso-http-cross-tenant-readsPASS
  • iso-http-cross-tenant-writesPASS
  • invites-expiredPASS
  • iso-http-idorPASS
  • iso-http-bad-uuidPASS
  • iso-db-context-scopingPASS
  • iso-db-cross-tenant-writes-blockedPASS
  • iso-db-membership-self-grant-blockedPASS
  • iso-db-posturePASS
  • iso-db-no-plaintext-tokensPASS
  • test-suitePASS

Versions lock

nextjs-postgres

next16.2.12
react19.2.8
react-dom19.2.8
pg8.22.0
zod4.4.3
@node-rs/argon22.0.2
typescript5.9.3
@types/node24.13.3
@types/pg8.20.3
@types/react19.2.18
@types/react-dom19.2.4
tailwindcss4.3.3
@tailwindcss/postcss4.3.3
class-variance-authority0.7.1
clsx2.1.1
tailwind-merge3.6.0
@radix-ui/react-slot1.3.3
tw-animate-css1.4.0
postgres18.4
node24.18.1

Scaffold integrity

2 scaffold files modified or deleted by the buyer-agent during verification
  • src/app/page.tsx
  • tsconfig.json

Every B2B SaaS eventually needs the same spine — orgs, roles, invites, sessions, and a guarantee that tenant A can never see tenant B's data — and it's the code teams reliably get wrong the first time, usually by finding out in a security review or a customer's pentest. This pack builds it: DB-backed sessions, multi-org membership with owner/admin/member roles, a full invite lifecycle, and Postgres row-level security as the actual enforcement wall, not just an app-layer check that hopes nobody forgets a `WHERE` clause. The receipt proves it: a 39-check, ~105-assertion verify suite — including cross-tenant probes run directly against the database as the app's own restricted role — has to pass before this pack ships.

Example use cases

  • **A solo founder** turning a single-user side project into a real team plan — adding "invite your team," role-gated admin controls, and per-org data before their first paid pilot.
  • **An agency** shipping the same B2B admin panel to multiple clients, each client isolated as its own org on shared infrastructure, without operating a separate database per customer.
  • **A dev-tools startup** adding organization support ahead of a security review, where "prove tenant data can't leak" is a question an enterprise buyer's security team will actually ask.
  • **A vertical SaaS team** (scheduling, ticketing, ops tooling) forking the pack's `notes` exemplar resource into their real per-tenant table — the org-scoped CRUD, permission checks, and RLS policies are already worked out; they copy the pattern instead of re-deriving it.

Why this pack

Everyone building multi-tenant SaaS chooses between four paths. Here's the honest comparison:

Bare agent, no packAuth SaaS (Clerk/Auth0-class)Hand-rolling it yourselfThis pack
**What it's for**Same goal, no pre-made decisionsAuthentication & session UX — genuinely excellent at thisThe same architecture, built from scratchOrg/role/tenancy layer, enforced in Postgres
**Cost**Token spend, repeated every time a leak is found in reviewRecurring per-MAU fee, indefinitelyEngineer time — days to weeks to get right$149 once + normal token spend to run the build
**Time**Looks fast; a few hours to something that runsFast for login/session screens specificallySlow — this is deliberately fiddly codeOne build pass, 3–4 sessions on a $20/mo-equivalent plan
**Correctness risk**High — agents reliably reach for exactly the bugs belowLow for auth itself, but doesn't touch data-layer tenant isolation at allHigh until proven otherwise — proving it is the hard partThe isolation suite ships 10 dedicated isolation checks inside the 39-check acceptance suite (~47 assertions in `scaffold/tests/verify/isolation.test.mjs`), probing at both HTTP (including IDOR) and direct SQL as the RLS-bound role; the entire 39-check suite runs against a real postgres:18.4 container
**Lock-in**None, but the pattern is undocumented and untestedYour identity data lives in a third party's systemNone, but the risk is entirely yoursNone — your code, your Postgres, no runtime dependency on this pack

**On the bare-agent column, specifically** — these are the classes of bug this pack pre-solves, not hypothetical:

  • Connecting the app as the Postgres **owner role** on a managed host. Neon and Railway both hand you an owner connection string by default, and row-level security does not bind the table owner — so RLS is silently off, and everything still looks like it works in testing.
  • **`USING`-only write policies.** A policy that correctly blocks cross-tenant reads but has no `WITH CHECK` still lets a request insert rows into another tenant's org.
  • **Pooler-unsafe session context.** Setting tenant identity with a session-scoped `SET` instead of a transaction-local `set_config(..., true)` leaks one request's org context into the next request that reuses the same pooled connection — invisible until concurrent traffic on a transaction-mode pooler exposes it.

**On the auth-SaaS column** — Clerk, Auth0, and similar are genuinely good at what they do: login screens, session UX, SSO, MFA. That's a different problem from this pack. An auth SaaS tells you *who* someone is; it says nothing about whether your database will hand tenant A's rows to tenant B once they're logged in. This pack is the tenancy-enforcement layer and pairs fine with an auth SaaS in front of it if you want one — it isn't a replacement for one, and this pack ships its own DB-backed sessions instead because owning exact invalidation semantics (revoke-on-password-change, no-stale-role-claims) is the actual product here.

**On hand-rolling** — what you're buying isn't the idea, it's the ~300-line frozen migration (schema, RLS policies, `SECURITY DEFINER` helpers, grants) and the 39-check verify suite that had to pass against it. That's the part that takes an experienced engineer real time to get exactly right and is easy to get subtly wrong in ways that don't show up until a specific bad request arrives.

We won't claim this pack makes your app unhackable — no pack can promise that. What the receipt backs is narrower and true: the specific cross-tenant leak classes above are closed by construction and asserted by an automated suite that connects to the database directly, not just through the app.

Scale envelope

Derived from the shipped architecture, not aspiration — and labeled as an engineering estimate, since it isn't load-tested at this pack's price point:

  • **Per-query cost of enforcement:** every tenant-scoped policy resolves through a `SECURITY DEFINER` helper (`is_org_member`) doing a single primary-key-indexed lookup on `org_members(org_id, user_id)` — not a join, not a scan. `org_id` is denormalized onto every tenant table specifically so policies never need one. In practical terms, RLS enforcement adds the cost of one indexed lookup per query, not a cost that grows with your data.
  • **Why the pooling design matters at scale:** identity is set with `set_config('app.user_id', ..., true)` inside an explicit transaction — `is_local=true` means it dies at COMMIT/ROLLBACK. That's what makes this safe under **transaction-mode connection pooling** (PgBouncer, Neon's pooler), which is the standard way to serve far more concurrent app requests than Postgres's raw connection limit allows. A session-scoped `SET` — the naive version — would leak context between requests sharing a pooled connection and simply doesn't work under transaction pooling. This design does.
  • **Estimate:** a single small managed Postgres instance (roughly the smallest paid tier on Railway or Neon — shared vCPU, a few hundred MB–1GB RAM, fronted by a transaction-mode pooler) should comfortably serve low thousands of orgs and tens of thousands of users at this schema's normal CRUD load. This is an estimate based on the indexing and policy shape, not a benchmark result.
  • **Where the first ceiling actually is:** not RLS policy cost — it's the same two things that limit any single-instance Postgres app: raw connection count (before you're pooling) and general write throughput/vacuum overhead as `org_members` and your tenant tables grow. Nothing about the RLS/GUC design imposes a lower ceiling than plain Postgres would have anyway.
  • **Upgrade path:** the standard Postgres playbook, unchanged by this pattern — bump instance size, add a read replica for read-heavy endpoints (member lists, notes lists), and only at genuinely large single-tenant volume consider partitioning a table like `notes` by `org_id`. Because `org_id` is already denormalized onto every tenant table, this schema is also a reasonable starting point for sharded/distributed Postgres (e.g. Citus-style) if you ever outgrow a single instance — that's a distant follow-on, not something this pack implements.

**What it costs to skip the pack (modelled, not measured):** building this spine yourself the way most people actually work — one agent on a top-tier model, no cheap-tier routing, no parallel subagents, no prompt-caching discipline — models out at roughly **$60–245 in API tokens, likely around $120**. That's this pack's own measured build volume repriced at Opus-class rates, multiplied by an exploration factor for the ~20 tenancy decisions in `ARCHITECTURE.md`; the pack-guided build itself measured ≈$6 of tokens per verification run (August 2026 rates), because tasks route down to cheaper tiers. Honest reading: at the likely figure that is **less** than the $149 price — the token math alone does not pay for this pack, and we won't pretend otherwise. What it pays for is the 39-check isolation suite, and the fact that the leak classes above are the ones you'd otherwise find in someone else's pentest report.

Verified against

  • claude-opus-5
  • claude-sonnet-5
  • claude-haiku-4-5

Supported stacks

  • nextjs-postgres
    next16.2.12
    react19.2.8
    react-dom19.2.8
    pg8.22.0
    zod4.4.3
    @node-rs/argon22.0.2
    typescript5.9.3
    @types/node24.13.3
    @types/pg8.20.3
    @types/react19.2.18
    @types/react-dom19.2.4
    tailwindcss4.3.3
    @tailwindcss/postcss4.3.3
    class-variance-authority0.7.1
    clsx2.1.1
    tailwind-merge3.6.0
    @radix-ui/react-slot1.3.3
    tw-animate-css1.4.0
    postgres18.4
    node24.18.1

Estimated buyer token cost

550,000 tokens for one build pass (estimate, not a guarantee).

FAQ

What if the build fails?

Run `bash scaffold/verify.sh` — it's deterministic and non-interactive, and prints one `CHECK: <name> PASS|FAIL` line per check; `ACCEPTANCE.md` maps every name to what it's asserting. If your agent's fix loop (`prompts/02-verify.md`) can't reach a clean pass after 5 iterations on a qualifying clean machine (Docker + Node 24.x, pack followed as written), it writes `FAILURE-REPORT.md` with the failing checks, hypotheses, and log tail instead of stopping silently. Full refund within 14 days of purchase if `verify.sh` fails on a supported configuration (the pinned stack and a model from the pack's matrix) — reply to your Stripe receipt email with the verify output or `FAILURE-REPORT.md` attached.

What models does this need?

`claude-opus-5` for the security core (T2/T3 — sessions, password auth, the tenancy pipeline — tagged `capable` on purpose), `claude-sonnet-5` for the feature build and verification (T4–T8, `standard`), and `claude-haiku-4-5` for environment setup (T1, `cheap`). Plan on **~550k fresh tokens** (input + output) for one full build pass, measured; the receipt separately reports ~7.7M tokens of total billed input (94% cache reads, billed near a tenth of fresh-input price) and a measured **$5.99 per verification run** — the most reliable of the three numbers. Expect **3–4 sessions** on a $20/mo-equivalent plan — driven by T2/T3's Opus-tier weight against Pro's rolling-window and weekly caps, not raw token volume (Anthropic doesn't publish an exact token-per-window figure, so treat the session count as an expectation, not a guarantee). If your plan is tighter, you can downgrade T2/T3 to `standard` and budget one extra verify/fix loop; the frozen test suite catches what a cheaper model misses.

Can I customize it after the build?

Yes. The `notes` resource is deliberately a worked exemplar meant to be copied for every real tenant table you add. The permission map is one file (`src/lib/auth/permissions.ts`), designed to be swapped for a DB-backed model later. Email delivery is one function (`notifyInvite()` in `src/lib/mail.ts`) waiting to be pointed at a real provider. SSO is a seam, not a feature — `auth_identities` is already keyed for adding an OIDC/SAML provider without touching sessions or tenancy.

Do I own the code?

Yes, entirely. No runtime license check, no dependency on OneShot at runtime, no phone-home. It's a plain Next.js + Postgres codebase — deploy it anywhere Node and Postgres run.

Is Postgres RLS enough for compliance (SOC 2, HIPAA, etc.)?

No, and we won't pretend otherwise. Row-level security is a real, verified *enforcement layer* inside your database — it's not a compliance certification. Frameworks like SOC 2 or HIPAA also require documented controls, access reviews, audit logging (not shipped in v1 — see Non-goals in PRD.md), incident response processes, and usually a third-party audit. This pack gives you a strong, tested technical answer to "can tenant data leak between customers" — it doesn't replace the paperwork and process compliance actually requires.

What if I'm not on Next.js + Postgres?

This pack currently ships one verified stack variant (`nextjs-postgres`, pinned versions in `pack.yaml`). If that's not your stack, it isn't verified for you yet.

Does this include SSO or MFA?

No — v1 ships the seams, not the implementation. `auth_identities(provider, provider_user_id)` exists so an OIDC/SAML provider slots in later without touching sessions or tenant logic; wiring an actual provider, and MFA, are explicit non-goals for this pack, documented in PRD.md.

Why enforce this in the database instead of just checking in app code?

Because a missed check in app code is exactly the bug class this pack exists to close — one forgotten `WHERE org_id = ...` and tenant A sees tenant B's rows, and nothing in a normal test suite catches it unless someone thinks to test that exact path. RLS makes the database refuse by default even when application code forgets, and the verify suite proves this by connecting directly as the app's own restricted database role and trying to break it — not just testing through the app's own (possibly buggy) checks.

Specific problems this pack pre-solves

Prescribed services

Disclosure: some links on this page are affiliate links. We may earn a commission if you sign up through them, at no extra cost to you. We only link to services the pack actually verified against.