# ADR 032: Better Auth

- HTML version: https://robbiepalmer.me/projects/recipe-site/adrs/032-better-auth
- Project: Recipe Site (https://robbiepalmer.me/projects/recipe-site.md)
- Status: Accepted
- Date: 2026-05-25

# Summary

This ADR chooses **[Better Auth](https://better-auth.com)** 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 household / friend-sharing permission model
* the authorization enforcement strategy
* the backend runtime, database, or deployment target

# Context

The recipe site is moving from a shared public collection toward authenticated, user-specific
state and 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 create hard vendor lock-in.
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](https://better-auth.com)** as the recipe site's authentication library.

Auth configuration lives in repository code. Social OAuth credentials live in the project's secrets
layer. The runtime, database, and deployment target are outside this ADR's scope.

At MVP, the intended scope is:

| Method               | MVP?         | Notes                                                       |
| -------------------- | ------------ | ----------------------------------------------------------- |
| **Google OAuth**     | **Yes**      | Expected primary sign-in path.                              |
| **GitHub OAuth**     | **Yes**      | Useful secondary path for technical friends.                |
| **Apple OAuth**      | **Deferred** | Add only if a real user need appears.                       |
| **Email + password** | **No**       | Not worth the support and security overhead.                |
| **Magic links**      | **Deferred** | Adds email-delivery and link-consumption edge cases.        |
| **Passkeys**         | **Deferred** | Worth revisiting later, not necessary for MVP.              |
| **First-party MFA**  | **Deferred** | Social providers handle MFA at the identity-provider layer. |

The initial shape is intentionally small:

```ts
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 is also outside this ADR's scope. 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

| Need                             | Better Auth fit                                                                                                                                                                                                         |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Code-first configuration**     | Auth is configured in TypeScript in the repo, which fits the project's agent-assisted workflow.                                                                                                                         |
| **Portability**                  | Built around modern web runtime primitives rather than a single framework or hosted platform.                                                                                                                           |
| **Predictable cost**             | Self-hosted library, no per-MAU pricing curve.                                                                                                                                                                          |
| **Household sharing foundation** | The [`organization` plugin](https://better-auth.com/docs/plugins/organization) provides a ready-made membership primitive.                                                                                              |
| **Future service / agent auth**  | Optional [`oauth-provider`](https://better-auth.com/docs/plugins/oauth-provider) and [`api-key`](https://better-auth.com/docs/plugins/api-key) plugins exist if the project later exposes APIs or agent-facing tooling. |
| **Maturity**                     | The 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:

| Component                            | Cost                                                                                                                                                                                                                               |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Better Auth library**              | **$0**. The framework itself is free and open source.                                                                                                                                                                              |
| **Better Auth Infrastructure**       | **Out of scope for MVP.** If adopted later: Starter is **$0/month**; Pro is **$20/month**; Enterprise is custom-priced.                                                                                                            |
| **Better Auth Infrastructure usage** | Starter 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 storage**       | Out of scope. 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:

| Provider          | Public pricing signal                                                                                                                                                                                                                                                                                                                                             | Why it matters here                                                                                                                |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Clerk**         | Free 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.                                 |
| **Auth0**         | Free 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.                                     |
| **Kinde**         | Free 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.   |
| **WorkOS**        | AuthKit 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 Auth** | Free 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.
* **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
* the chosen runtime or database 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.

---

Markdown index of this site: https://robbiepalmer.me/llms.txt
