<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bastien GUILLAUME</title>
    <description>The latest articles on DEV Community by Bastien GUILLAUME (@bronan).</description>
    <link>https://dev.to/bronan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3859320%2F2e59949d-cc5b-4d87-9e47-bab90dc917ed.jpg</url>
      <title>DEV Community: Bastien GUILLAUME</title>
      <link>https://dev.to/bronan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bronan"/>
    <language>en</language>
    <item>
      <title>Enterprise features are a tax. We paid it once, in the open.</title>
      <dc:creator>Bastien GUILLAUME</dc:creator>
      <pubDate>Wed, 16 Sep 2026 14:27:50 +0000</pubDate>
      <link>https://dev.to/bronan/enterprise-features-are-a-tax-we-paid-it-once-in-the-open-450o</link>
      <guid>https://dev.to/bronan/enterprise-features-are-a-tax-we-paid-it-once-in-the-open-450o</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;We run eight products. Seven of them authenticate with &lt;a href="https://better-auth.com" rel="noopener noreferrer"&gt;better-auth&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@alphabros/enterprise&lt;/code&gt; is on npm, MIT-licensed, version 0.1.0.&lt;/strong&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/ShipFast-Syndicate/enterprise" rel="noopener noreferrer"&gt;github.com/ShipFast-Syndicate/enterprise&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;One package, four entry points, no framework-specific server code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/server&lt;/code&gt;&lt;/strong&gt; — 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 &lt;strong&gt;Users and Groups&lt;/strong&gt;, two-factor, passkeys, API keys, and two of our own: a &lt;strong&gt;hash-chained audit log&lt;/strong&gt; and an &lt;strong&gt;organization policy&lt;/strong&gt; engine (require 2FA, enforce SSO with a break-glass owner, session lifetime, allowed sign-in methods, group-to-role mapping).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/schema&lt;/code&gt;&lt;/strong&gt; — the Drizzle tables, a plain SQL migration, and a CLI: &lt;code&gt;ab-enterprise migrate&lt;/code&gt; copies the migration into your project, &lt;code&gt;ab-enterprise verify&lt;/code&gt; checks the &lt;strong&gt;live&lt;/strong&gt; database has every table and column (migrations that never reached production are a real failure mode; we check after every deploy), &lt;code&gt;ab-enterprise audit-verify&lt;/code&gt; recomputes an organization's audit chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/client&lt;/code&gt;&lt;/strong&gt; — the matching client plugins, plus &lt;code&gt;discoverHomeRealm(email)&lt;/code&gt;: the "email first, then route to your IdP" step every enterprise login page needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/portal&lt;/code&gt;&lt;/strong&gt; — 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 &lt;strong&gt;no colours, fonts or spacing of their own&lt;/strong&gt; — you map your design tokens onto &lt;code&gt;--ab-*&lt;/code&gt; variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one thing the package does not decide is who pays for what. You pass a &lt;code&gt;resolveEntitlements(orgId)&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five minutes to a working setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;betterAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;better-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;enterprisePreset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@alphabros/enterprise/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;betterAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;drizzleAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;enterprisePreset&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myapp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secretsKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENTERPRISE_SECRETS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ≥ 32 chars, from your secret store&lt;/span&gt;
      &lt;span class="na"&gt;resolveEntitlements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;planFeatures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ab-enterprise migrate &lt;span class="nt"&gt;--out&lt;/span&gt; drizzle   &lt;span class="c"&gt;# drops the SQL migration into your folder&lt;/span&gt;
npx ab-enterprise verify                  &lt;span class="c"&gt;# against TURSO_DATABASE_URL after deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ab-security-settings&lt;/span&gt; &lt;span class="na"&gt;org-id=&lt;/span&gt;&lt;span class="s"&gt;"org_123"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ab-security-settings&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Design choices, and why
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Embedded, not central.&lt;/strong&gt; 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 (&lt;code&gt;studio_ref&lt;/code&gt; columns, per-organization SSO config), but not as the first step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built on better-auth's own plugins where they exist.&lt;/strong&gt; &lt;code&gt;organization&lt;/code&gt;, &lt;code&gt;sso&lt;/code&gt;, &lt;code&gt;scim&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;twoFactor&lt;/code&gt;, &lt;code&gt;passkey&lt;/code&gt; and &lt;code&gt;apiKey&lt;/code&gt; 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).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Tamper-evident audit log.&lt;/strong&gt; Each organization's events form a hash chain. &lt;code&gt;verify&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did about security before publishing
&lt;/h2&gt;

&lt;p&gt;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 (&lt;code&gt;zod&lt;/code&gt;). 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 &lt;code&gt;docs/security.md&lt;/code&gt;, with the mitigation and the test that guards it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not, yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pilot-grade.&lt;/strong&gt; 0.1.0 has 397 tests and no production tenant yet. Two of our products go first; expect breaking changes before 1.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinned to better-auth 1.6.33.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your billing, your rules.&lt;/strong&gt; No Stripe integration, no pricing page. One function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No central login across products.&lt;/strong&gt; Each app is its own tenant boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>security</category>
      <category>saas</category>
    </item>
    <item>
      <title>Building an AI first Startup Studio</title>
      <dc:creator>Bastien GUILLAUME</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:58:25 +0000</pubDate>
      <link>https://dev.to/bronan/building-an-ai-first-startup-studio-20p5</link>
      <guid>https://dev.to/bronan/building-an-ai-first-startup-studio-20p5</guid>
      <description>&lt;p&gt;This post was &lt;strong&gt;NOT&lt;/strong&gt; generated by AI 🤖.&lt;/p&gt;

&lt;p&gt;Our first post was a little over two months ago. About &lt;em&gt;50 billion tokens&lt;/em&gt; later, we're due for an update (and we feel they were well spent).&lt;/p&gt;

&lt;p&gt;Basically, we've spent those two months rebuilding ourselves thanks to the lessons learned from &lt;a href="//klar.alphabros.eu"&gt;Klar&lt;/a&gt; launch so this post is mainly going to be technical.&lt;/p&gt;

&lt;p&gt;Let's start with some number :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The team grew 📈 — we welcomed Spirit (Taking care of strategy and ops) and Claude (Infra), so the agent team is now 8.&lt;/li&gt;
&lt;li&gt;We added one startup to our catalogue — &lt;strong&gt;Nomi&lt;/strong&gt;, a payroll &amp;amp; HR solution for SMBs (an interesting one to build, because it runs straight into legislation) (coming soon to the &lt;a href="https://alphabros.eu" rel="noopener noreferrer"&gt;studio landing page&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;We moved from &lt;em&gt;10&lt;/em&gt; projects to &lt;em&gt;30&lt;/em&gt; (mainly our internals that we have been rebuilding and are finally tracking)
Studio corpus today: ~1,633 tasks (~1,465 done) across those projects, an 850-feature / 83-epic / 92-defect QA tree, and ~200 decision records (50 ADRs + FDR/PRD/RFC).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We moved a lot technically : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are nearly fully IaC: ~92% IaC-covered — ~1443 managed resources (our gap is mostly managed SaaS - Turso's DBs plus a handful of click-ops items like R2/tunnels/B2/OVH/Tailscale).&lt;/li&gt;
&lt;li&gt;We are K8s ready (but not using it yet):

&lt;ul&gt;
&lt;li&gt;We have containerized all our projects&lt;/li&gt;
&lt;li&gt;We have Kustomize manifests for all our projects&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We have a full fledged deployment control plane (including an ODE)&lt;/li&gt;
&lt;li&gt;We have continuous deployment through our entreprise bus — merge to main → event bus → the fleet reconciles and deploys.&lt;/li&gt;
&lt;li&gt;We have continous integration : automated code review + merge across multiple orgs.&lt;/li&gt;
&lt;li&gt;High Availability everywhere / ~120 self-healing units / 35+ scheduled jobs / a sourceS-of-truth control plane with drift detection / mostly self-hosted infra (~70% of our infra plane / 80% of our always on runtime / 100% of our compute).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We moved also product wise :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We closed ~&lt;strong&gt;2500&lt;/strong&gt; PRs (merged ~2200). Most of them (~1500) were internals (chore / fix / ci / etc.) but we have been moving on features also (&lt;strong&gt;384&lt;/strong&gt; PRs).&lt;/li&gt;
&lt;li&gt;We have a &lt;em&gt;content pipeline&lt;/em&gt; that produces posts and articles weekly, per project, ready to publish to X, LinkedIn, Dev.to and a custom blog — with a human in the loop when we want one (or not, of course).&lt;/li&gt;
&lt;li&gt;We have a &lt;em&gt;product pipeline&lt;/em&gt; constantly looking for improvements, whether in UI/UX or new features (without forgetting the bug chasing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the last few days the gaps have closed and everything converged where we wanted it. We're about to un-gate the main producer we paused while rebuilding — so hopefully our next post will be a launch 🚀.&lt;/p&gt;

&lt;p&gt;Last but not least — cost: everything we've produced so far is still under the &lt;strong&gt;thousand-euro&lt;/strong&gt; barrier (we only use plans for AI and we size our VMs carefully).&lt;/p&gt;

&lt;p&gt;Bastien, for the team.&lt;/p&gt;

&lt;p&gt;Disclaimer: this post was NOT generated by AI — but everything else was.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>news</category>
    </item>
    <item>
      <title>Test from Publishr</title>
      <dc:creator>Bastien GUILLAUME</dc:creator>
      <pubDate>Fri, 03 Apr 2026 11:03:30 +0000</pubDate>
      <link>https://dev.to/bronan/test-from-publishr-41ge</link>
      <guid>https://dev.to/bronan/test-from-publishr-41ge</guid>
      <description>&lt;p&gt;Test content #test&lt;/p&gt;

</description>
      <category>test</category>
    </item>
  </channel>
</rss>
