SaaS Boilerplate

B2B SaaS Starter 2026: SSO + RBAC + Audit Logs Reality Check

B2B SaaS starter SSO + RBAC + audit logs reality check: 8 requirements most kits skip. SAML, SCIM, audit retention, IP allowlist. Tested in production.

Huifer
Huifer
May 16, 20269 min read
Also available in:Deutsch · 中文

Written by Huifer, solo developer and maintainer of TanStack Ship. I have shipped three B2B SaaS products since 2023 — two at $50k+ MRR, one at $500k+ MRR — and the SSO + RBAC + audit log RFP comes in by month 6 on every one. This post is the 8-item checklist that survives the reality check, the three failure modes that page me at 2am, and the primary-source specs each item pulls from. "Out of the box" means production-ready, not "scaffolded in a tutorial" — the difference is ~600 engineer-hours per item if you skip the boilerplate.

Verified sources: SAML 2.0 Specifications (OASIS) · SCIM 2.0 Protocol (RFC 7644) · Better Auth SSO Plugin · OWASP Logging Cheat Sheet · GDPR Article 17 (Right to Erasure) · SOC 2 Trust Services Criteria · NIST 800-63B Digital Identity Guidelines · Stripe Billing API · OWASP Access Control Cheat Sheet

Last updated: 2026-05-16 · Changelog


TL;DR: A B2B SaaS starter kit that says "SSO + RBAC + audit logs out of the box" means eight production-grade requirements, not three checkboxes: (1) SAML 2.0 SP-initiated SSO with IdP-initiated fallback, (2) SCIM 2.0 user provisioning with deprovisioning, (3) RBAC with org > workspace > role inheritance, (4) Audit log trail with retention + GDPR pseudonymization, (5) Session audit with IP + user-agent, (6) IP allowlist enforcement, (7) MFA via TOTP or WebAuthn, (8) Per-seat billing with SSO seat reconciliation. Three "out of the box" claims that mean nothing: SAML signing-only (no IdP-initiated), SCIM without deprovisioning, audit logs with 30-day retention. TanStack Ship ships all eight; most 2026 kits ship three.


B2B SaaS Starter 2026: SSO + RBAC + Audit Logs Reality Check

What "out of the box" actually means

"Out of the box" in a B2B starter context means production-ready by week 1 of the buyer's evaluation — not "scaffolded," not "works in development," not "we left a TODO in the SCIM handler." For a kit to credibly claim SSO + RBAC + audit logs out of the box, it has to clear eight specific requirements. Most 2026 kits clear three.

I have shipped three B2B SaaS products on Cloudflare Workers + Better Auth + D1 since 2023 — two at $50k+ MRR, one at $500k+ MRR. The SSO + RBAC + audit log RFP arrives by month 6 on every one. This post is the checklist that survives the buyer call and the engineering math.

The 8-item reality check

1. SAML 2.0 SP-initiated SSO + IdP-initiated fallback (94 hours)

The buyer has Okta, Entra ID, Google Workspace, or JumpCloud and wants single-click login from the IdP dashboard. The 94 hours: 28 on SP-initiated flow (your app generates an AuthnRequest, IdP returns a signed assertion), 22 on IdP-initiated (unsolicited assertion, no prior AuthnRequest), 18 on signature + assertion validation per the SAML 2.0 spec, 26 on just-in-time provisioning with role mapping.

The trap: signed assertion vs signed response. Many IdPs sign only the assertion; some sign only the response; some rely on TLS. Validate BOTH per the spec. In TanStack Ship the Better Auth SSO plugin handles both flows. The 94 hours becomes 4 hours of IdP metadata config.

2. SCIM 2.0 user provisioning + deprovisioning (78 hours)

SCIM lets the IdP push user lifecycle events (create, update, deactivate) without manual admin work. The 78 hours: 32 on the RFC 7644 /Users and /Groups endpoints, 24 on the deprovisioning handler (must revoke active sessions within 60 seconds), 22 on group → role mapping.

The trap: deprovisioning propagation gap. The IdP sends DELETE; my handler revoked the user record but left 30 active sessions alive in Cloudflare KV. Users stayed logged in 6 hours. The fix is a per-session revocation list checked on every request, OR a session version counter the deprovisioning event increments.

3. RBAC with org > workspace > role inheritance (88 hours)

RBAC for B2B is not flat. A user is "owner" at the org level, "admin" in workspace A, "member" in workspace B. The role inheritance bug is when code checks "is this user an admin anywhere?" and grants cross-workspace privileges. The 88 hours: 28 on the org-role table, 32 on the workspace-role table with cross-workspace lookups explicitly forbidden, 28 on the policy enforcement layer. A production B2B RBAC policy with org inheritance:

typescript
// app/lib/rbac/org-policy.ts
import { createAccessControl } from "better-auth/plugins/access"
import { defaultStatements, adminAc } from "better-auth/plugins/admin/access"

const statement = {
  ...defaultStatements,
  org: ["settings:read", "settings:write", "billing:read", "billing:write",
        "members:read", "members:invite", "members:remove", "audit:read"],
  workspace: ["create", "read", "update", "delete", "members:invite"],
  sso: ["config:read", "config:write", "metadata:read"],
  audit: ["read", "export"],
} as const

export const ac = createAccessControl(statement)

export const owner = ac.newRole({
  ...adminAc.statements,
  org: ["settings:read", "settings:write", "billing:read", "billing:write",
        "members:read", "members:invite", "members:remove", "audit:read"],
  workspace: ["create", "read", "update", "delete", "members:invite"],
  sso: ["config:read", "config:write", "metadata:read"],
  audit: ["read", "export"],
})

export const admin = ac.newRole({
  org: ["settings:read", "members:read", "members:invite", "audit:read"],
  workspace: ["create", "read", "update", "members:invite"],
  sso: ["config:read", "metadata:read"],
  audit: ["read"],
})

export const member = ac.newRole({
  org: ["settings:read"],
  workspace: ["read"],
})

export const billing_admin = ac.newRole({
  org: ["settings:read", "billing:read", "billing:write"],
})

export const auditor = ac.newRole({
  org: ["settings:read", "audit:read", "audit:export"],
  workspace: ["read"],
})

The auditor and billing_admin roles matter for SOC2-readiness — buyers want to grant read-only access to their internal audit team without making them full admins.

A SAML SP-initiated SSO handler in TanStack Ship:

typescript
// app/routes/api/sso/saml/callback.ts
import { sso } from "~/lib/auth"
import type { APIRoute } from "astro"

export const POST: APIRoute = async ({ request, locals }) => {
  const formData = await request.formData()
  const samlResponse = formData.get("SAMLResponse") as string
  const orgSlug = formData.get("RelayState") as string

  // Validate signature on response AND assertion per SAML 2.0 spec
  const result = await sso.verifySAMLResponse(samlResponse, {
    validateResponseSignature: true,
    validateAssertionSignature: true,
    wantAssertionsSigned: true,
    audience: env.SAML_SP_ENTITY_ID,
    recipient: `${env.APP_URL}/api/sso/saml/callback`,
  })

  if (!result.valid) {
    return new Response("Invalid SAML response", { status: 401 })
  }

  // JIT provisioning: create user on first login
  const user = await sso.provisionUser({
    email: result.attributes.email,
    orgSlug,
    role: result.attributes.role ?? "member",
  })

  return sso.createSessionResponse(user, locals)
}

4. Audit log trail with retention + GDPR pseudonymization (96 hours)

Audit logs are "who did what, when, from where, and the before/after diff." Compliance teams ask on day 1 of enterprise sales; logs are also the single largest storage cost in a multi-year deployment. The OWASP logging cheat sheet is the spec.

The 96 hours: 36 on the write path (audit event with actor, target, action, before/after state, IP, user-agent, org-id, workspace-id), 28 on the read path (audit-log UI with filters), 32 on retention + GDPR Article 17 pseudonymization.

The trap: retention-vs-GDPR conflict. A user requests data deletion; the audit log contains a row with their email. Naive implementation deletes the user record but leaves the audit row referencing a non-existent user. The fix is to pseudonymize on deletion: keep the row, replace the email with a salted hash, mark the actor as deleted-user-{hash}. This satisfies GDPR AND keeps the audit trail intact. The 96 hours becomes 4 hours of retention-config edits.

5. Session audit with IP + user-agent (44 hours)

Every session event (login, logout, MFA challenge, password change, role change) emits an audit event tagged with IP + user-agent. Buyers ask: "show me every IP that logged in as this user in the last 90 days." The 44 hours: 24 on the session-event emit hook (Better Auth subscribes to lifecycle events), 12 on IP geolocation (MaxMind GeoLite2), 8 on suspicious-session detection (two countries in 5 minutes → flag).

6. IP allowlist enforcement (38 hours)

Most B2B buyers at $100k+ ARR require IP allowlisting for admin operations. The trap: CDN egress IPs. Buyer's traffic comes through Cloudflare's edge IPs, not their corporate IPs. If you allowlist the corporate IPs, you reject everything. The fix is to check cf-connecting-ip (the true client IP forwarded by Cloudflare), not the immediate connection IP. The Cloudflare CF-Connecting-IP docs cover the spec.

7. MFA via TOTP or WebAuthn (52 hours)

MFA is non-negotiable for the enterprise tier. Buyers want TOTP (RFC 6238, OWASP MFA Cheat Sheet) or WebAuthn platform authenticators (NIST 800-63B AAL2+). Email-only MFA is rejected; SMS MFA is rejected (SIM swap risk per NIST).

8. Per-seat billing with SSO seat reconciliation (62 hours)

The buyer pays per-seat; the seat count is the union of SCIM-provisioned + manually-invited users. Reconciliation runs nightly — SCIM deprovisions 5 users, the seat count drops by 5, the next invoice reflects the lower count. The trap: SCIM deprovisioning drops seats, but Stripe has already invoiced the month. The reconciliation marks the change as effective next-cycle and credits the unused portion. The Stripe webhook idempotency guide applies.

The 8-item matrix, scored against the 2026 starter kit landscape

#RequirementTanStack ShipMakerkitShipFastWasp / Open-SaaSNext-Forge
1SAML SP + IdP-initiated SSO✅✅❌❌❌
2SCIM 2.0 + deprovisioning✅⚠️ manual❌❌⚠️ manual
3RBAC org > workspace inheritance✅✅⚠️ flat❌⚠️ flat
4Audit logs + GDPR pseudonymization✅⚠️ partial❌❌❌
5Session audit + IP + UA✅⚠️ partial❌❌❌
6IP allowlist enforcement✅⚠️ manual❌❌❌
7MFA via TOTP or WebAuthn✅✅⚠️ TOTP only❌⚠️ TOTP only
8Per-seat billing + SSO reconciliation✅✅⚠️ flat❌⚠️ flat

The ✅ / ⚠️ / ❌ reflects production-ready at week 1. Makerkit ships good RBAC and SAML but partial audit logging. ShipFast ships flat RBAC and TOTP only — SAML and SCIM are community plugins. Wasp / Open-SaaS ships none of the eight. Next-Forge ships flat RBAC and TOTP only.

The 552-hour math

Build pathEngineer-hoursWall-clock (solo)Wall-clock (2-person)
From scratch (8 items)5524–5 months2–3 months
TanStack Ship starter243–4 days2 days
Make-it-up-as-you-go200–3503–5 months2–3 months

The from-scratch figure includes the seven traps plus integration work (RBAC ↔ audit logs ↔ SCIM ↔ SSO). The 24-hour TanStack Ship figure assumes the team has picked the product surface and is configuring to their IdP and Stripe price IDs.

Where the "out of the box" claim falls apart

Three "out of the box" claims on 2026 starter kit landing pages that mean nothing:

  1. "SAML SSO included" — but only SP-initiated. The buyer clicking the Okta tile gets a 403 because your handler rejects unsolicited assertions.
  2. "SCIM provisioning" — but only on the create path. SCIM DELETE is ignored; user is still active 6 hours later. SOC2 fail.
  3. "Audit logs enabled" — but with 30-day retention. SOC2 wants 1 year, HIPAA 6 years. 30 days fails every questionnaire.

If your kit hits any of these three, it is "scaffolded and waiting 200 hours to fill the gaps."

FAQ

Q: How long does it take to ship a B2B SaaS with all 8 requirements on TanStack Ship?

A: 3–4 days of IdP metadata config + Stripe setup + 1–2 weeks of UI.

Q: Can I skip SCIM if my buyer doesn't ask for it?

A: Yes, but expect the question on the next RFP. SCIM is the second-most-common question after SSO - if you skip it now you will add it later under time pressure. The SSO + RBAC deep-dive covers why skipping both is the most common mistake.

Q: Does TanStack Ship include a SAML IdP or just the SP side?

A: Just the SP side. Your buyer brings their own IdP (Okta, Entra ID, Google Workspace).

Q: How does this compare to Clerk, Auth0, or WorkOS?

A: Those are managed auth services, not boilerplates. Clerk and Auth0 charge per MAU; WorkOS per B2B connection. TanStack Ship is one-time license; you own the auth surface and pay only Cloudflare edge fees.

Q: What about HIPAA, PCI-DSS, FedRAMP?

A: Out of scope for the eight above. Each adds a compliance audit (200+ hours, 8–12 weeks for SOC2 Type II). Retention scripts are pre-wired (1-year default); the audit itself is a CPA engagement.


Ship the 8 B2B requirements in 24 hours, not 552. See TanStack Ship's auth + SSO + SCIM modules → or compare against other SaaS boilerplates.