← Insights
build

Enterprise SSO, RBAC, and Audit Logs From Day One

Your first enterprise prospect just sent a security questionnaire demanding SAML, SCIM, RBAC, and audit trails you don't have. How to build them so they survive

A 200-line spreadsheet just landed in your inbox. It's a security questionnaire from the first enterprise prospect big enough to triple your ARR. Row 14 asks for SAML single sign-on. Row 31 asks for SCIM provisioning. Row 52 asks for "granular role-based access control." Row 88 asks for "tamper-evident audit logs retained for at least one year." Their procurement team will not sign without these, and their security review is in three weeks.

You have none of it. You have a users table with an is_admin boolean and a created_at column you've been calling an audit trail. Your auth is email and password, hardcoded against your own database. And the engineer who's loudest in standup just said "I can bolt SAML on in a sprint."

He can't. Not the version that passes this review. Enterprise readiness isn't a feature you bolt on — it's an architectural property, and three of the rows on that spreadsheet are load-bearing decisions you should have made before you wrote your first migration. The good news: it's never too late to make them correctly, and it's a lot cheaper than losing the deal.

What this costs you when you fake it

The tempting move is to special-case this one customer. Add a SAML login path that only their domain uses. Hand-roll a permissions check or two. Start writing audit rows into a table from the four places you remember to.

Then their auditor asks a question your hack can't answer. "Show me every action this terminated employee took in the last 90 days." Your audit table has rows from four code paths and silence from forty. "Demonstrate that a support agent cannot read another tenant's billing data." Your is_admin boolean has no answer. "Show me the access change history for this role." There's no history; roles are columns.

Now you're rebuilding under deadline, in production, with a signed deal hanging on it. The deal that was worth, say, $180k ARR is now also costing you two engineers for six weeks of emergency work — call it $180k of payroll — because the foundation couldn't answer questions the foundation was never designed to answer. Build it right the first time and that same foundation closes the next ten enterprise deals without a single new line of auth code.

RBAC that survives org-chart reality

Most teams model permissions as roles on a user: admin, member, viewer, stored as a string. This survives exactly until a customer says "our team leads can manage their own department's users but not billing, and our auditors can read everything but change nothing." Your three string roles can't express that, so you add a fourth, then a fifth, then a permissions_override JSON column, and now nobody can answer "who can do what" without reading code.

Build it as roles that map to permissions, not roles that are permissions. A permission is a verb on a resource: invoices:read, users:invite, billing:write. A role is a named bundle of permissions. A user holds roles within a scope — an org, a team, a project. Your authorization check asks one question everywhere: does this user hold a role, in this scope, that includes this permission?

// the only authz question your code ever asks
can(user, "invoices:read", { org: orgId })

This survives because new customer requirements become new bundles, not new code. Custom roles, scoped delegation, read-only auditors — all data, not branches. And it's enforced in one place, a single guard the rest of the codebase calls, so there's exactly one thing to audit instead of four hundred scattered if (user.is_admin) checks waiting to drift.

SSO done right means SSO isn't special

The mistake that fails security reviews is treating SSO as an alternate login path. The enterprise customer wants their identity provider to be the source of truth: when they offboard someone in Okta, that person loses access to your product within minutes, not whenever you remember to delete the row.

That's two protocols, and you need both. SAML or OIDC for authentication — the user logs in through their IdP and you trust the assertion. SCIM for provisioning — the IdP pushes user creates, updates, and deactivations to you automatically. SAML without SCIM means a terminated employee can still log in until someone manually removes them, which is exactly the gap the questionnaire is probing for.

Architecturally, this works when authentication is a layer that sits in front of your user model, not tangled into it. Your app should ask "who is this authenticated principal and what's their stable external ID," and not care whether they arrived via password, Google, or a SAML assertion from a Fortune 500 IdP. Get that indirection right once and adding the next enterprise customer's IdP is a configuration row, not a code change. Get it wrong — auth logic welded to the password flow — and every new identity provider is a fresh integration.

Audit logs procurement will actually accept

A updated_at timestamp is not an audit log. An audit log answers, for any security-relevant action: who did it, what they did, to which resource, from where, and when — and it can prove the record wasn't altered after the fact.

Three properties separate a real audit log from a table that happens to be named one:

It's complete. Every privileged action writes an event, which means audit logging can't be 47 scattered log() calls you remember to add. It has to live at a chokepoint — the same authorization guard that already sees every sensitive operation emits the event. One place to enforce, impossible to forget.

It's immutable. Audit events are append-only. No UPDATE, no DELETE, ideally enforced at the database permission level so application code can't tamper even if compromised. Auditors ask specifically whether the system that produces the logs can also edit them; "no, by construction" is the answer that passes.

It's queryable and retained. "Show me everything user X did between these dates" has to return in seconds, and the data has to survive for the retention period the contract specifies, typically one year minimum. That means structured fields — actor, action, resource, timestamp, IP — not a freeform text blob you'll have to grep through during an incident.

Build this as infrastructure, emitted automatically by your authz layer, and you produce evidence for compliance without anyone hand-instrumenting endpoints. We built exactly this pattern into a multi-tenant platform where the audit trail had to satisfy enterprise procurement on day one. See how we approached it →

What fixed looks like

Fixed is a permission model where authorization is one function the whole codebase calls, roles are data, and "who can do what in which scope" is a query, not a code review.

Fixed is authentication as a layer in front of your user model, so SAML, OIDC, and SCIM plug in as configuration and a new enterprise IdP ships in a day instead of a sprint. Offboarding in their IdP revokes access in yours, automatically, within minutes.

Fixed is an append-only audit trail emitted at the authorization chokepoint, immutable by database permission, queryable in seconds, retained for the contractual window. When the auditor asks "what did this terminated employee do," you run one query and move on.

Most of all, fixed is the security questionnaire becoming a form you fill in, not a fire drill — because the architecture already answers every row.

This is for you if

You're a founder with a real enterprise deal on the table and a security questionnaire you can't currently pass, and you'd rather build the foundation once than re-fake it for every customer. We retrofit RBAC, SSO, and audit logging as proper architecture — typically $50k+ to design and implement the permission and audit layer on an existing product, $100k+ if it comes with multi-tenant isolation and a full enterprise-readiness pass that turns the next ten questionnaires into paperwork. A scoped audit and remediation plan starts at $25k+.

This is not for you if you have zero enterprise prospects and are building SAML because a blog post said you should — you're solving a problem you don't have yet and the requirements will change before you sell. It's not for you if you have a security team that's already built this correctly and just needs a code review. And it's not for you if you want the cheapest possible checkbox that lets you claim SSO without the SCIM and audit pieces that actually pass review — that hack costs more when the auditor finds it than doing it right costs now.