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:
- Social + email/password login out of the box.
- Two-factor (
twoFactor()) — TOTP and OTP flows. - Passkeys/WebAuthn (
passkey()) — including conditional UI, so the browser autofills a saved passkey with no button-press ceremony. (Full passkey walkthrough here.) - Multi-tenant organizations (
organization()) — orgs, members, and invitations for team products.
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.



