Projects/Recipe Site/Architecture Decisions

ADR 032: Better Auth

Summary

This ADR proposes Better Auth as the authentication layer for the recipe site.

This is a decision about authentication, not the full backend or authorization model. It covers how users sign in and how account identity is represented. It does not decide:

  • the eventual backend runtime or database
  • the household / friend-sharing permission model
  • whether any hosted auth operations platform is adopted later

Context

The recipe site is moving from a shared public collection toward user-specific features: favorites, cooking history, household sharing, and eventually friend sharing. That requires a real notion of user identity.

The auth choice needs to satisfy a small number of constraints:

  1. Config should live in code. This project leans heavily on agent-assisted workflows. Agents can read and edit TypeScript or Terraform; they cannot reliably own a SaaS dashboard.
  2. The choice should stay portable. Auth should not pre-decide the future backend ADR.
  3. Costs should stay predictable. A personal project does not need per-MAU pricing pressure.
  4. Social OAuth is the primary sign-in model. Google is the default path; GitHub is a useful secondary provider. Email/password is out of scope.
  5. MVP can tolerate some operational ownership. At inner-circle scale, owning the runtime is acceptable if it keeps the system simpler and more portable.

The market context matters mainly because it reduces decision risk. In 2025, the OSS TypeScript auth space consolidated around Better Auth: Lucia was sunset in March 2025, and Auth.js's official migration path now points to Better Auth after the September 2025 merger. In 2026, Better Auth is not an experimental contrarian pick; it is the main code-first OSS option in this category.

Decision

Use Better Auth as the recipe site's authentication library.

Auth configuration should live in repository code. Social OAuth credentials should live in the project's secrets layer. The exact runtime, database, and deployment target remain deferred to a future backend ADR.

At MVP, the intended scope is:

MethodMVP?Notes
Google OAuthYesExpected primary sign-in path.
GitHub OAuthYesUseful secondary path for technical friends.
Apple OAuthDeferredAdd only if a real user need appears.
Email + passwordNoNot worth the support and security overhead.
Magic linksDeferredAdds email-delivery and link-consumption edge cases.
PasskeysDeferredWorth revisiting later, not necessary for MVP.
First-party MFADeferredSocial providers handle MFA at the identity-provider layer.

The initial shape is intentionally small:

export const auth = betterAuth({
  database: adapter(db),
  socialProviders: {
    google: {
      clientId: env.GOOGLE_CLIENT_ID,
      clientSecret: env.GOOGLE_CLIENT_SECRET,
    },
    github: {
      clientId: env.GITHUB_CLIENT_ID,
      clientSecret: env.GITHUB_CLIENT_SECRET,
    },
  },
  plugins: [organization()],
});

Better Auth's hosted Infrastructure product is explicitly out of scope for MVP. This ADR chooses the library, not a hosted control plane. Hosted dashboards, audit logs, or extra abuse protection can be a later operational decision if usage justifies them.

The authorization model remains a separate future ADR. Better Auth gives us stable identity and a household membership primitive; it does not settle how recipe visibility and sharing rules should work.

Why Better Auth

NeedBetter Auth fit
Code-first configurationAuth is configured in TypeScript in the repo, which fits the project's agent-assisted workflow.
PortabilityBuilt around modern web runtime primitives rather than a single framework or hosted platform.
Predictable costSelf-hosted library, no per-MAU pricing curve.
Household sharing foundationThe organization plugin provides a ready-made membership primitive.
Future service / agent authOptional oauth-provider and api-key plugins exist if the project later exposes APIs or agent-facing tooling.
MaturityThe broader OSS TypeScript auth market has consolidated around it rather than away from it.

Two points matter most:

  1. The source of truth is code. That makes the auth layer reviewable, diffable, and compatible with how the rest of the project is built.
  2. The operational shape matches the project. At MVP scale, a small self-hosted library is a better fit than paying for a large hosted auth platform before the problem actually demands one.

Cost Model

As of May 27, 2026, the cost picture is straightforward:

ComponentCost
Better Auth library$0. The framework itself is free and open source.
Better Auth InfrastructureOut of scope for MVP. If adopted later: Starter is $0/month; Pro is $20/month; Enterprise is custom-priced.
Better Auth Infrastructure usageStarter includes 1 dashboard seat, 10,000 audit logs/month, and 1,000 security detections/month. Pro includes unlimited seats, 20,000 audit logs/month then $0.0001/event, and 10,000 security detections/month then $0.001/event.
Google / GitHub OAuth$0 at the auth-provider layer for normal use.
Email / SMS$0 at MVP because email/password, magic links, and first-party MFA are deferred. If Better Auth Infrastructure Pro is adopted later, its pricing page lists $0.001/email and $0.09/SMS.
Database / session storageDeferred to the backend ADR. This ADR does not pre-decide those costs.

The practical implication is that the MVP auth decision does not add a mandatory monthly SaaS line item. The first real paid cost only appears if the project later chooses hosted operational features rather than staying with the library alone.

Cost Shape Of SaaS Alternatives

The reason pricing still weighs against the hosted options is not just "they cost money." It is that several of them introduce usage dimensions that map directly to the roadmap: monthly active users, machine-to-machine traffic, abuse checks, or paid operational add-ons.

As of May 27, 2026, the most decision-relevant cost signals are:

ProviderPublic pricing signalWhy it matters here
ClerkFree up to 50,000 MRU. Pro starts at $20/month billed annually. M2M tokens are priced by usage after free monthly allowances: $0.001 per token creation after 2,500/month and $0.00001 per verification after 100,000/month.Human-user auth is inexpensive early, but machine traffic introduces a separate metered cost axis.
Auth0Free up to 25,000 external active users. Pricing page shows 1,000 M2M authentications on Free and Essentials, 5,000 on Professional and Enterprise, with M2M add-ons on paid tiers. Essentials starts at $35/month.The cost shape is more sensitive to API and agent usage because M2M is explicitly quota-based.
KindeFree includes 10,500 MAU. Pro starts at $25/month. Extra MAU are billed above the included tier. Free includes M2M applications; Pro advertises uncapped M2M tokens.More favorable than Clerk/Auth0 on M2M cost, but still introduces a MAU-based pricing curve once usage grows past the free tier.
WorkOSAuthKit is free up to 1 million monthly active users. Separate add-ons are priced independently, for example Radar starts free for 1,000 checks then $100 per 50,000 checks, and custom domains are $99/month. WorkOS Connect supports M2M, but the public pricing page does not expose token-based M2M pricing as clearly as Clerk or Auth0.End-user auth cost is generous, but the overall price surface becomes more modular as more platform capabilities are turned on.
Supabase AuthFree includes 50,000 MAU. Pro starts at $25/month with 100,000 MAU included, then $0.00325 per MAU.Straightforward MAU pricing with predictable early cost, but still a per-user growth curve rather than infrastructure-shaped cost.

The comparison is less about who has the lowest sticker price and more about which pricing axis becomes load-bearing if the roadmap succeeds. Better Auth's library avoids both a per-MAU curve and a separate M2M token meter. That matters because the planned future shape includes not just human users, but potentially agents, MCP tooling, background services, and third-party API access.

Alternatives Considered

Dashboard-First SaaS: Clerk, Kinde, WorkOS, Supabase Auth

These products offer polished managed auth, prebuilt UI, and vendor-owned operations. Those are real advantages.

They are rejected because their configuration surface mostly lives in vendor dashboards. That is the wrong fit for a project that wants auth to behave like the rest of the codebase. Supabase Auth is an even worse fit because it effectively pre-decides the wider backend platform.

Auth0

Auth0 is the strongest SaaS alternative and the clearest escape hatch if the self-hosted path stops being worth it.

Its first-party Terraform provider gives it the only serious code-first configuration story in the SaaS auth category. If a hosted platform becomes necessary, Auth0 is the most credible option because configuration can still live in code.

It is rejected for now because:

  • the product is heavier than this project needs
  • pricing at growth is less predictable; as of May 27, 2026 the pricing page shows a free tier up to 25,000 external active users, plus a separate M2M quota of 1,000 on Free/Essentials and 5,000 on Professional/Enterprise
  • it adds a vendor relationship and platform surface area we do not currently need

Other OSS Libraries

  • Auth.js: rejected because its own maintainers now point greenfield users toward Better Auth.
  • Lucia: rejected because it was sunset in March 2025.
  • Stack Auth: credible, but weaker on adoption, runtime story, and overall fit than Better Auth.

Roll Your Own

Rejected. Re-implementing OAuth, session management, CSRF protection, cookie handling, and account linking would be a large amount of undifferentiated work.

Consequences

Positive

  • Auth config stays in the repo. Changes are reviewed in PRs instead of hidden in a dashboard.
  • The backend ADR stays open. Auth does not force a database or hosting choice up front.
  • Costs stay simple. There is no per-user SaaS pricing curve.
  • Household sharing gets a head start. The organization primitive covers membership without forcing us to invent it from scratch.
  • The support surface stays small. Social OAuth-first avoids password resets, password storage, and most first-party auth UX complexity.

Negative

  • We own the runtime. Rate limiting, abuse mitigation, admin tooling, and data-deletion flows remain our responsibility unless we later adopt hosted operational tooling.
  • Apple Sign In would add maintenance if adopted later. In particular, the Apple client secret rotation burden would sit with us rather than a SaaS provider.
  • Dependency review matters. Better Auth moves quickly, so upgrade hygiene is part of the cost.
  • There is some business-risk overhang. Better Auth is VC-backed; if the OSS path degrades, we may need to revisit sooner than planned.

When To Revisit

Revisit this ADR if any of the following become true:

  • public signup materially increases abuse or support burden
  • hosted audit logs, dashboards, or stronger managed security controls become necessary
  • enterprise SSO / compliance requirements appear
  • Better Auth's runtime support or maintenance quality drops
  • a future backend ADR surfaces a hard incompatibility with this choice

If the self-hosted library path stops paying for itself, Auth0 is the documented fallback, because it preserves the same code-first operating model better than any other hosted option.