What Is Better Auth? The Open-Source Auth Library Vibe Coders Love

Email/password, Google login, magic links, and two-factor auth — all free, all in your own codebase, with zero vendor lock-in. Here's what Better Auth does and why AI sets it up faster than anything else.

TL;DR

Better Auth is a free, open-source TypeScript library that adds login to any app — email/password, Google, GitHub, magic links, two-factor auth — without paying Clerk or Auth0 a dime. You own your user data, it works with any framework, and AI can set it up in minutes. The tradeoff: you need your own database.

Why AI Coders Need to Know About Better Auth

Here's the moment every vibe coder hits: you've built something real, people are starting to use it, and then you check your Clerk bill. Twenty dollars. Fifty dollars. More as it grows — all just for the login screen. Or you're using Auth0, and the free tier runs out, and suddenly the configuration hell begins.

Authentication is table stakes. Every app needs it. But the popular services — Clerk, Auth0, Firebase Auth — charge you for it. Not because they have to, but because you're kind of stuck once you've built around their SDK. Swapping out auth later is painful.

Better Auth is the answer to that trap. It's an open-source authentication library that you drop directly into your codebase. It handles everything the paid services handle — email and password login, OAuth with Google, GitHub, and 30+ other providers, magic link login, two-factor authentication, session management, and user management — completely free, because it runs in your own app talking to your own database.

The reason vibe coders specifically love it: AI generates Better Auth code remarkably well. The library is TypeScript-first, well-documented, and follows consistent patterns. You describe what you want, AI builds the whole thing, and the result actually works. You spend zero time fighting with an external dashboard, no OAuth app setup beyond what you'd do anyway, no billing page, no support tickets.

If you're building something that other people will log into — and aren't ready to pay per-user fees forever — Better Auth is worth understanding right now.

The Real-World Scenario

What You Prompted

"Add authentication to my Next.js app using Better Auth. Users should be able to sign up with email and password, or log in with Google. Store users in my Postgres database. Protect the /dashboard route so only logged-in users can access it."

That single prompt generates a complete, production-ready auth system. But before you run it, here's what's actually happening — because when something breaks, you'll need to know which piece to look at.

Better Auth is solving four problems at once:

Let's look at exactly what AI generates for each of these.

What AI Generated: The Complete Better Auth Setup

A typical Better Auth install touches five areas: the server config, the database schema, the client helper, the API route, and your protected pages. Here's each one.

1. Install the Package

npm install better-auth

That's it. No separate client and server packages to keep in sync, no peer dependency maze. One package, everything included.

2. The Auth Server Config (The Whole Security System)

This is the heart of Better Auth. Think of it as the blueprint for your building's entire security system — what IDs it accepts, where it stores records, and what the master keys are.

// lib/auth.ts
import { betterAuth } from "better-auth"
import { prismaAdapter } from "better-auth/adapters/prisma"
import { prisma } from "./prisma" // your existing Prisma client

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
  secret: process.env.BETTER_AUTH_SECRET!,
  baseURL: process.env.BETTER_AUTH_URL!,
})

What each part does:

3. The Database Schema

Better Auth needs specific tables in your database to store users, sessions, accounts (OAuth connections), and verification tokens. You generate these automatically:

npx better-auth generate

This command reads your auth config and produces a Prisma schema migration (or Drizzle schema, depending on your setup). Then you apply it:

npx prisma migrate dev --name add-auth

After this runs, your database has four new tables:

This is the key difference from NextAuth's JWT-only mode: your data lives in your database, not in cookies. You can query users directly, build an admin panel, export data, and never be held hostage by a vendor.

4. The API Route Handler

Better Auth needs one catch-all API route to handle all auth requests — sign up, sign in, sign out, OAuth callbacks, session checks:

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

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

What this does: Creates an endpoint at /api/auth/* that Better Auth controls. The [...all] catch-all means it handles /api/auth/sign-in/email, /api/auth/sign-up/email, /api/auth/callback/google, and everything else from one file.

5. The Client Helper

Better Auth generates a typed client that your frontend uses to make auth calls. This is what makes it work so well with AI — everything is typed, so AI knows exactly what methods and parameters exist:

// lib/auth-client.ts
import { createAuthClient } from "better-auth/react"

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_APP_URL,
})

You import this client in your components to sign in, sign up, sign out, and check the current user.

6. Sign-Up and Sign-In Components

// components/SignInForm.tsx
"use client"
import { authClient } from "@/lib/auth-client"
import { useState } from "react"

export function SignInForm() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")

  const handleEmailSignIn = async () => {
    await authClient.signIn.email({
      email,
      password,
      callbackURL: "/dashboard",
    })
  }

  const handleGoogleSignIn = async () => {
    await authClient.signIn.social({
      provider: "google",
      callbackURL: "/dashboard",
    })
  }

  return (
    <div>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button onClick={handleEmailSignIn}>Sign In</button>
      <button onClick={handleGoogleSignIn}>Sign in with Google</button>
    </div>
  )
}

7. Checking the Session and Protecting Routes

// app/dashboard/page.tsx
import { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { redirect } from "next/navigation"

export default async function Dashboard() {
  const session = await auth.api.getSession({
    headers: await headers(),
  })

  if (!session) {
    redirect("/sign-in")
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
      <p>Email: {session.user.email}</p>
    </div>
  )
}

What this does: In Next.js App Router, pages are server components by default. Better Auth's auth.api.getSession() runs on the server, reads the session cookie from the request headers, validates it against the database, and returns the user object — or null if nobody's logged in. If there's no session, the user gets redirected to the sign-in page.

Understanding Each Part

Plugins: The Real Power Move

Better Auth's plugin system is where it separates from the competition. Instead of paying for "premium features," you add official plugins to your config:

import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
import { magicLink } from "better-auth/plugins"
import { organization } from "better-auth/plugins"

export const auth = betterAuth({
  // ...base config...
  plugins: [
    twoFactor(),       // TOTP 2FA (Google Authenticator, Authy)
    magicLink(),       // Passwordless email login links
    organization(),    // Multi-tenant teams and workspaces
  ],
})

Two-factor auth is a feature that Clerk charges $25/month for on their Pro plan. With Better Auth, it's three lines. Magic links, organizations with roles and invitations, passkeys, anonymous sessions — all free, all open-source.

Sessions: Cookie-Based, Database-Backed

By default, Better Auth uses cookie-based sessions that are validated against your database on every request. The cookie contains a session token (a random string), and when a request comes in, Better Auth looks up that token in the session table to get the user.

This is more secure than pure JWT cookies because you can invalidate a session instantly by deleting the database row — like remotely deactivating a building access card. With JWT-only auth, once a token is issued, there's no way to cancel it until it expires naturally.

The tradeoff: every authenticated request hits your database. For most apps this is fine. For very high-traffic apps, you'd add caching.

OAuth Under the Hood

When a user clicks "Sign in with Google," Better Auth handles the entire OAuth 2.0 flow: redirecting to Google, receiving the authorization code, exchanging it for tokens, fetching the user's profile, and either creating a new user record or linking to an existing one. You configure your Google credentials once and Better Auth handles the rest.

The account table stores the connection between your user and each OAuth provider. One user can have multiple accounts — their Google login and GitHub login linked to the same profile.

Environment Variables You'll Need

# .env.local
BETTER_AUTH_SECRET=your-long-random-secret-here
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000

# For Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# Your database (you need this anyway)
DATABASE_URL=postgresql://user:password@localhost:5432/myapp

Generate your secret with: openssl rand -base64 32

Better Auth vs Clerk vs NextAuth vs Auth0

Choosing an auth solution is one of the most consequential early decisions in a project. Here's the honest breakdown:

Feature Better Auth Clerk NextAuth Auth0
Cost Free forever Free to 10k MAU, then $25+/mo Free (self-hosted) Free to 7.5k MAU, then $23+/mo
Vendor lock-in None — your code, your data High — Clerk owns the user store None High
Database required Yes No (Clerk hosts it) Optional (JWT mode) No (Auth0 hosts it)
Setup difficulty Medium (~1 hour) Easy (~15 min) Medium (~45 min) Hard (2-4 hours)
Prebuilt UI components No (build your own) Yes (beautiful ones) Basic sign-in page Yes (Universal Login)
Two-factor auth Yes (free plugin) Yes (paid plan) Manual implementation Yes (paid plan)
Magic links Yes (free plugin) Yes Yes (Email provider) Yes (Passwordless)
Organizations/teams Yes (free plugin) Yes (paid plan) Manual Yes (paid plan)
AI codegen quality Excellent Excellent Good (v4/v5 confusion) Mediocre
Framework support Any JS/TS framework React-first, others ok Next.js-first, others ok Any

When to Use Each One

Use Better Auth when: You want full control, you have a database already, you're building something that might scale, or you're tired of paying auth vendors. It's the right default for most serious projects.

Use Clerk when: You want a working sign-in page in 15 minutes, you don't want to think about auth at all, or you're building a demo/prototype and don't care about costs yet. Clerk's prebuilt UI components are genuinely beautiful and require zero HTML.

Use NextAuth when: You're in a Next.js app, you don't want a database, and you just need social login. JWT-only sessions are its killer feature — zero infrastructure required.

Use Auth0 when: Your company's security team is already mandating it, you need advanced enterprise features like SAML/SSO, or you're integrating with existing Auth0 infrastructure. Don't choose it for greenfield projects.

What AI Gets Wrong About Better Auth

Better Auth is newer than NextAuth or Clerk, which means AI models trained before late 2024 may not have it in their training data. When it does generate Better Auth code, here are the most common issues.

1. Missing the Schema Migration Step

What happens: Everything looks right in code, you try to sign up, and you get a database error like relation "user" does not exist or Cannot read properties of undefined.

Why AI gets this wrong: AI generates the auth config correctly but sometimes skips the npx better-auth generate step in its instructions. The database tables don't exist until you run this command and apply the migration.

The fix: After installing and configuring Better Auth, always run these two commands before starting your app:

npx better-auth generate
npx prisma migrate dev --name add-auth

If you're using Drizzle instead of Prisma, the generate command outputs a Drizzle schema file instead. Then run npx drizzle-kit push.

2. Wrong Import Paths

What happens: TypeScript errors like Module '"better-auth"' has no exported member 'betterAuth' or Cannot find module 'better-auth/react'.

Why AI gets this wrong: Better Auth uses a subpath export pattern. Different things come from different subpaths:

// Core server config — from root
import { betterAuth } from "better-auth"

// Adapters — from subpath
import { prismaAdapter } from "better-auth/adapters/prisma"

// React client — from subpath
import { createAuthClient } from "better-auth/react"

// Plugins — from subpath
import { twoFactor } from "better-auth/plugins"

// Next.js handler — from subpath
import { toNextJsHandler } from "better-auth/next-js"

AI sometimes imports everything from the root package, which doesn't work. If you see import errors, check that each import is coming from the right subpath.

3. Client vs. Server Confusion

What happens: Your component crashes with "hooks can only be used in client components" or session is always null on the dashboard page.

Why AI gets this wrong: Better Auth has two different ways to check sessions:

AI sometimes uses the client hook in a server component, or the server function in a client component. If your dashboard is a server component (the default in Next.js App Router), use the server function. If it's marked "use client", use the hook.

4. OAuth Callback URLs Not Configured

What happens: You click "Sign in with Google," pick your account, and land on an error page. Sometimes it says redirect_uri_mismatch.

Why AI gets this wrong: Just like with NextAuth, you have to tell Google (or GitHub, or whatever provider) exactly which URLs are allowed to receive the auth callback. AI generates the code correctly but can't configure the external service for you.

The fix: In your Google Cloud Console, add this as an authorized redirect URI:

# Development:
http://localhost:3000/api/auth/callback/google

# Production:
https://yourdomain.com/api/auth/callback/google

The pattern with Better Auth is always {your-base-url}/api/auth/callback/{provider-name}.

5. Stale AI Knowledge

What happens: AI generates Better Auth code that doesn't compile, uses APIs that don't exist, or references a package structure that changed.

Why AI gets this wrong: Better Auth launched in 2024 and is actively evolving. Some AI models have outdated training data for it specifically.

The fix: When asking AI to set up Better Auth, paste in the current docs. Tell it: "Here's the current Better Auth documentation for Next.js. Use this exact pattern." The library's docs are at better-auth.com and are accurate — use them as your source of truth, not AI's training data.

How to Debug Better Auth Issues

When something's broken, work through this list before pasting errors into AI. Half of these you can fix yourself in two minutes.

Step 1: Check the Database Tables Exist

Open your database browser (Prisma Studio, TablePlus, pgAdmin, whatever you use) and verify these four tables exist: user, session, account, verification.

If they're missing, you skipped the generate/migrate step. Run it now:

npx better-auth generate
npx prisma migrate dev --name add-auth

Step 2: Verify Environment Variables

// Temporary debug in your auth.ts — remove before deploying
console.log({
  secret: process.env.BETTER_AUTH_SECRET ? "SET" : "MISSING",
  url: process.env.BETTER_AUTH_URL,
  googleId: process.env.GOOGLE_CLIENT_ID ? "SET" : "MISSING",
  db: process.env.DATABASE_URL ? "SET" : "MISSING",
})

Add this temporarily and restart your dev server. Check your terminal output. Any "MISSING" values are your problem.

Step 3: Test the API Endpoints Directly

Open these in your browser to confirm Better Auth is running:

A 404 on any /api/auth/* URL means your route handler file is in the wrong location or named incorrectly. It must be at app/api/auth/[...all]/route.ts.

Step 4: Check the Session Cookie

After signing in, open browser dev tools → Application tab → Cookies. You should see a better-auth.session_token cookie. If it's missing after sign-in appears to succeed, the session creation failed silently — usually a database write error.

What AI Is Good at Fixing vs. What You Fix Yourself

Fix yourself:

Good for AI:

Frequently Asked Questions

What is Better Auth?

Better Auth is a free, open-source authentication library for TypeScript and JavaScript. It gives you email/password login, OAuth with Google/GitHub/etc., magic links, and two-factor auth — all self-hosted in your own codebase. Unlike Clerk or Auth0, there's no vendor, no monthly bill, and your user data stays in your own database.

Is Better Auth really free?

Yes, completely. Better Auth is MIT-licensed open-source software. You run it in your own app, it stores users in your own database, and there are no usage fees, no user limits, and no paid tiers. The only costs are your own infrastructure — a database and a server to run your app, which you'd have anyway.

What frameworks does Better Auth work with?

Better Auth works with any framework that runs TypeScript or JavaScript on the server. That includes Next.js, Nuxt, SvelteKit, Remix, Astro, Hono, Express, Fastify, and Elysia. It has a framework-agnostic core and provides specific integration guides for each. AI tools generate Better Auth code well for all of these.

Better Auth vs Clerk — which should I use?

Clerk is easier to set up for a first project (prebuilt UI components, no database needed) but costs money beyond 10,000 monthly active users and locks you into their platform. Better Auth takes a bit more setup but is completely free, gives you full control of your user data, and has no vendor lock-in. For serious projects or anything that might scale, Better Auth is worth the extra hour of setup.

Does Better Auth require a database?

Yes. Because Better Auth is self-hosted, it stores users and sessions in your own database. It works with any SQL database (PostgreSQL, MySQL, SQLite) via a database adapter. You'll usually pair it with Prisma, Drizzle, or Kysely. This is different from NextAuth's JWT-only mode, which can run without a database using JWT cookies.

What to Learn Next

Better Auth handles the mechanics of authentication, but there's a broader ecosystem of concepts worth understanding once it's running: