B2B SaaS template for Next.js, workspaces, RBAC, audit, SSO
B2B SaaS templates fall into two camps: pretty marketing shells that lie about tenancy, and serious foundations that handle the unsexy enterprise primitives. The difference shows up the first time a buyer's IT security reviewer asks where row isolation is enforced or whether audit logs are immutable. SaaSForge Core is built for the second camp.
Workspaces, tenancy as a database boundary
Each customer lives in a workspace. Every domain table (projects, documents, comments) carries a `workspace_id` foreign key, and Postgres RLS policies enforce that queries can only see rows where the authenticated user is a member. Application code does not have to remember to add `where workspace_id = ?`, the database refuses to return rows otherwise.
Workspaces have explicit ownership transfer, member invitations via Resend, settings flows, and a switcher in the dashboard. The plumbing is real, not a `team_id` column bolted onto a single-tenant demo.
RBAC, four roles, granular permissions
Owner controls billing and ownership transfer. Admin manages members and settings. Member works on the product. Viewer reads but does not write. The four-role surface is what most B2B SaaS converges on; the boilerplate ships it with explicit permission checks on mutations.
The permission system is granular enough to define new actions (e.g., `documents.delete`, `billing.read`) without rewriting the role table. UI hides what users cannot do; the API rejects what the UI hides. Both layers exist because hide-only UIs leak through API testing tools.
Audit logs and impersonation, the support workflow
Every mutation writes an audit log row: actor, resource, metadata, timestamp. That table is what compliance reviews ask about and what support uses to investigate 'who deleted my thing'. Audit log rows are not editable through normal flows; they are a write-only ledger.
Support impersonation is gated and audited. A support user can assume a customer's context (read-only by default) to debug an issue; every impersonation session shows up in the audit log so the customer can verify after the fact. The combination, impersonation for debugging, audit for accountability, is the table-stakes B2B support workflow.
create table audit_logs (
id uuid primary key default gen_random_uuid(),
workspace_id uuid not null references workspaces(id),
actor_user_id uuid not null,
action text not null,
resource_type text not null,
resource_id uuid,
metadata jsonb,
ip_address inet,
created_at timestamptz not null default now()
);SSO, 2FA, API keys, IP allowlisting, the procurement checklist
IT security reviews ask a predictable list of questions: Is MFA available? Is SSO supported? Are API keys rotated and masked? Is access restrictable by IP? SaaSForge Core ships TOTP 2FA via Supabase Auth, SAML SSO hooks for Okta/Azure AD/Google Workspace, API keys with secret masking after creation, and optional IP allowlisting for workspace access.
None of these are bolted on after the demo, they are first-class patterns in the codebase. The point is that when procurement sends the security questionnaire, your answers are 'yes, and here is the implementation' rather than 'we will add it next quarter'.
Stripe subscriptions wired to workspaces
Subscriptions are workspace-scoped, not user-scoped. The Owner controls billing; member changes do not affect the subscription state. Stripe Checkout handles new purchases; the Customer Portal handles plan changes. The webhook lifecycle (create, update, renew, cancel) is fully wired with idempotency tracking.
Three plan tiers (Starter/Pro/Enterprise) with monthly and yearly options ship as the default; swap price IDs and your billing matches your Stripe products. The webhook handler is documented end-to-end so you know what to extend versus what to leave alone.