DEV Community

Bastien GUILLAUME
Bastien GUILLAUME

Posted on Originally published at alphabros.eu

Enterprise features are a tax. We paid it once, in the open.

Every B2B product hits the same wall. A prospect's IT team asks for SSO through their identity provider. Then for SCIM, so accounts appear and disappear with their HR system. Then for an audit log they can hand to a compliance officer, and a rule that says "everyone in our domain must log in through our IdP, no exceptions". None of it makes the product better for the people who use it every day. All of it is table stakes for the contract.

We run eight products. Seven of them authenticate with better-auth, each with its own copy of the same setup, its own database and its own Stripe account. Building that wall seven times was never going to happen. Buying it per connection from a US vendor didn't fit either. So we built it once, as a package every product embeds, and we published it.

@alphabros/enterprise is on npm, MIT-licensed, version 0.1.0.
Source: github.com/ShipFast-Syndicate/enterprise.

What you get

One package, four entry points, no framework-specific server code:

  • /server — a preset of better-auth plugins: organizations with teams and roles, SSO (SAML 2.0 and OIDC) with DNS domain verification and just-in-time provisioning, SCIM 2.0 provisioning for Users and Groups, two-factor, passkeys, API keys, and two of our own: a hash-chained audit log and an organization policy engine (require 2FA, enforce SSO with a break-glass owner, session lifetime, allowed sign-in methods, group-to-role mapping).
  • /schema — the Drizzle tables, a plain SQL migration, and a CLI: ab-enterprise migrate copies the migration into your project, ab-enterprise verify checks the live database has every table and column (migrations that never reached production are a real failure mode; we check after every deploy), ab-enterprise audit-verify recomputes an organization's audit chain.
  • /client — the matching client plugins, plus discoverHomeRealm(email): the "email first, then route to your IdP" step every enterprise login page needs.
  • /portal — seven Web Components (Lit) that make the whole thing self-serve for a customer's IT admin: members, an SSO wizard (paste IdP metadata, verify the domain by DNS TXT, do a mandatory test login, then enforce), SCIM tokens, security policy, API keys, and an audit viewer with CSV export and chain verification. They work unchanged in SvelteKit, Astro, Next and plain HTML, and they ship no colours, fonts or spacing of their own — you map your design tokens onto --ab-* variables.

The one thing the package does not decide is who pays for what. You pass a resolveEntitlements(orgId) function that reads your own billing plan and returns the features that organization has. Every portal endpoint answers 403 without the feature. Entitlements stay in your product; the package only enforces them.

Five minutes to a working setup

// auth.ts
import { betterAuth } from "better-auth";
import { enterprisePreset } from "@alphabros/enterprise/server";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "sqlite" }),
  plugins: [
    ...enterprisePreset({
      product: "myapp",
      secretsKey: process.env.ENTERPRISE_SECRETS_KEY!, // ≥ 32 chars, from your secret store
      resolveEntitlements: async (orgId) => planFeatures(await getPlan(orgId)),
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode
npx ab-enterprise migrate --out drizzle   # drops the SQL migration into your folder
npx ab-enterprise verify                  # against TURSO_DATABASE_URL after deploy
Enter fullscreen mode Exit fullscreen mode
<ab-security-settings org-id="org_123"></ab-security-settings>
Enter fullscreen mode Exit fullscreen mode

That's the whole integration. The SSO wizard, SCIM endpoints and audit log are live behind your existing /api/auth handler.

Design choices, and why

Embedded, not central. We deliberately did not build an identity service. Each product keeps its own users, its own database and its own IdP connections. A central identity plane is a single point of failure and a user migration; we'll get there later, and the data model already leaves room for it (studio_ref columns, per-organization SSO config), but not as the first step.

Built on better-auth's own plugins where they exist. organization, sso, scim, admin, twoFactor, passkey and apiKey are upstream. We added what upstream lacks: SCIM Groups on the 1.6 line (mapped onto organization teams, with a role-mapping table), the audit chain, the policy engine, the entitlement gate, the portal, and the secrets wrapper that encrypts IdP client secrets at rest with your key (AES-256-GCM, bound to the provider row).

It runs on Cloudflare Workers. Four of our products deploy there. SAML on workerd was the open question; we proved samlify's signing and verification, encrypted assertions included, under nodejs_compat before writing a line of the package.

Tamper-evident audit log. Each organization's events form a hash chain. verify recomputes it; retention works by compaction with a chained anchor rather than deletion, so a year-old log still verifies. It's honest about its limit: someone with write access to your database can rewrite the chain, which is why the CLI exists and why we recommend exporting checkpoints.

What we did about security before publishing

We ran two audits before the first publish, one on the repository and supply chain, one on the code, and a whole-branch review after the fixes. They found real things: an audit-log injection across tenants, a role recompute that could demote an organization owner through a SCIM group change, a retention purge that broke the chain, an unused encryption key. All of it is fixed, each with a regression test that reproduces the original exploit. The production dependency tree is a single package (zod). Releases are published from CI through npm Trusted Publishing with provenance; there is no long-lived publish token. The one accepted advisory on the 1.6 line is documented in docs/security.md, with the mitigation and the test that guards it.

What it is not, yet

  • Pilot-grade. 0.1.0 has 397 tests and no production tenant yet. Two of our products go first; expect breaking changes before 1.0.
  • Pinned to better-auth 1.6.33. The SSO and SCIM plugins for 1.7 are a breaking peer change; we stay on 1.6 until the fleet moves, and the package moves with it.
  • Your billing, your rules. No Stripe integration, no pricing page. One function.
  • No central login across products. Each app is its own tenant boundary.

Contributing

Issues are open on the repository; that's the intake for now, and every issue becomes a tracked task on our side. Security findings go through GitHub's private vulnerability reporting, not a public issue. Pull requests from outside the organization aren't accepted yet; we'll open that up once the pilots are through.

If your product has been putting off the enterprise tier because it's a tax with no upside for your users, this is the tax paid once. Use it.


Alpha Bros is a studio building small B2B products from Paris. The enterprise layer is the first piece of our platform we've published; more will follow as they prove themselves in our own products.

Top comments (0)