What it is: Better Auth is an open-source, MIT-licensed authentication library for TypeScript that runs inside your application instead of on someone else's servers. Login, sessions, two-factor, passkeys, and multi-tenant organizations — all in your codebase, all writing to your database. About 29,000 GitHub stars, framework-agnostic, and free.

Who makes it: The Better Auth open-source project (better-auth/better-auth), MIT-licensed and written in TypeScript. It's one of the fastest-rising auth projects of the last two years, and the reason is boring in the best way: it does the whole job, not a slice of it.

Why it's here: Hosted auth providers meter your users and hold their identity records. Better Auth's per-user cost is structurally zero — it's a library, so you pay for your database and nothing else. For a solo founder or a small team watching the burn, that's not a rounding error; it's the difference between a flat database bill and an invoice that grows with every signup. (For the full cost-at-scale case against renting, see Better Auth vs Clerk vs Auth0.)

Start it in about ten minutes#

Install the package:

npm install better-auth

Create the server instance — a secret, your database, and whichever methods you want turned on:

// lib/auth.ts
import { betterAuth } from "better-auth";
import { passkey } from "better-auth/plugins/passkey";

export const auth = betterAuth({
  secret: process.env.BETTER_AUTH_SECRET,
  database: yourDatabaseAdapter,        // Postgres / MySQL / SQLite
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  plugins: [passkey()],                 // passwordless, WebAuthn
});

Mount the handler on one catch-all route (Next.js shown; the pattern is the same for SvelteKit, Nuxt, Remix, Astro, or plain Node):

// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth);

Generate the schema (npx @better-auth/cli generate), and you have working email/password, GitHub login, and passkeys writing to your own tables. Signing a user in from the client is one call:

import { authClient } from "@/lib/auth-client";

await authClient.signIn.social({ provider: "github" });
// or passwordless:
await authClient.signIn.passkey();

What you actually get#

Everything you'd normally rent ships as a plugin you switch on:

The honest tradeoff#

You're taking on the security surface. Sessions, rate-limiting, credential storage, and keeping the library patched are your job now, not a vendor's. Better Auth hands you tested defaults for all of it, but the operational duty moves in-house.

That's the deal, stated plainly: you trade a vendor's per-user invoice and their custody of your users for a flat database bill and your own responsibility. For most product-shaped apps — especially early ones optimizing for runway and optionality — that's the trade you want. And it's the one migration you can't easily do later: keeping identity data in your own database from day one is free; clawing it back from a hosted provider is not.