Better Auth: The Game‑Changer JS Authentication Has Been Waiting For

Better Auth: The Game‑Changer JS Authentication Has Been Waiting For

Authentication in the JavaScript ecosystem has been a patchwork for years: you either wire up lots of library glue or outsource to a SaaS and give up control. Better Auth flips that script. It’s framework‑agnostic, runs inside your app, keeps data in your DB, and ships a plugin system for advanced features—without per‑user pricing.

This post shows why it’s different and how to get running with copy‑pasteable snippets you can adapt today.


Why Better Auth stands out

  • Framework‑agnostic: works with Next.js, Remix, Nuxt, SvelteKit, Express, Hono, Fastify, and more
  • Built‑in features: email+password, social SSO, 2FA, passkeys, multi‑session, rate limiting, orgs/roles
  • Plugin ecosystem: add features via plugins instead of bespoke code
  • Keep your data: users stay in your database; no per‑user costs
  • Full control: customize flows, cookies, endpoints, and types, end‑to‑end

Quick start on Next.js: from zero to sign‑in

1) Install

# pick your package manager
pnpm add better-auth
# npm i better-auth / yarn add better-auth / bun add better-auth

2) Create the server instance (email+password + Next server action cookies)

// lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    // autoSignIn: true by default; set to false to require manual sign-in after sign-up
    autoSignIn: false,
  },
  // Ensure cookies are set automatically in Next.js Server Actions/RSC
  plugins: [nextCookies()], // keep this as the last plugin
});

3) Mount the handler (Next.js App Router)

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

// Mount Better Auth under /api/auth/*
export const { GET, POST } = toNextJsHandler(auth.handler);

Client setup (Next.js React client)

// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
  // baseURL: "http://localhost:3000" // set if your API is on a different origin
});

Simple flows you’ll use daily

Email & Password (Next.js)

Sign up/sign in with Server Actions (cookies auto-set via nextCookies plugin):

// app/(auth)/actions.ts
"use server";
import { auth } from "@/lib/auth";

export async function signUp(formData: FormData) {
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  await auth.api.signUpEmail({
    body: { email, password },
  });
}

export async function signIn(formData: FormData) {
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  await auth.api.signInEmail({
    body: { email, password },
  });
}

Social login (e.g., GitHub)

Configure provider (server):

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

export const auth = betterAuth({
  // ...other options
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

Trigger from a Client Component:

// app/sign-in/SignInButtons.tsx
"use client";
import { authClient } from "@/lib/auth-client";

export function SignInButtons() {
  return (
    <button
      onClick={() =>
        authClient.signIn.social({
          provider: "github",
          callbackURL: "/dashboard",
          errorCallbackURL: "/error",
          newUserCallbackURL: "/welcome",
        })
      }
    >
      Continue with GitHub
    </button>
  );
}

Access session data

Client hook (Client Component):

// app/_components/User.tsx
"use client";
import { authClient } from "@/lib/auth-client";

export function User() {
  const { data: session, isPending } = authClient.useSession();
  if (isPending) return null;
  return session ? (
    <span>{session.user.email}</span>
  ) : (
    <button onClick={() => authClient.signOut()}>Sign out</button>
  );
}

Server (RSC or route handler):

// app/dashboard/page.tsx (RSC)
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export default async function DashboardPage() {
  const session = await auth.api.getSession({ headers: await headers() });
  if (!session) return null; // or redirect("/sign-in")
  return <div>Welcome {session.user.email}</div>;
}

Add power with one line: plugins

Two‑Factor Authentication (2FA):

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

export const auth = betterAuth({
  // ...other options
  plugins: [twoFactor()],
});

Client usage (React client):

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

export const authClient = createAuthClient({
  plugins: [twoFactorClient({ twoFactorPage: "/two-factor" })],
});

Passkeys (WebAuthn, Client Component):

// app/sign-in/PasskeyButton.tsx
"use client";
import { authClient } from "@/lib/auth-client";

export function PasskeyButton() {
  return (
    <button
      onClick={() =>
        authClient.signIn.passkey({ email: "john@doe.com", callbackURL: "/dashboard" })
      }
    >
      Sign in with Passkey
    </button>
  );
}

Anonymous (guest) sign‑in:

await authClient.signIn.anonymous();

API Keys (for server‑to‑server):

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

export const auth = betterAuth({
  // ...other options
  plugins: [apiKey()],
});

Production tips and gotchas (Next.js)

  • API route: mount via toNextJsHandler(auth.handler) under /api/auth/[...all].
  • Cookies: secure and httpOnly in production by default; configure advanced.useSecureCookies, custom names, and cross‑subdomain cookies as needed.
  • CORS/Origins: when calling across domains, set trustedOrigins on the server and configure CORS in your framework.
  • Next.js middleware: for fast redirects, check for a session cookie; for secure checks, validate with auth.api.getSession() inside routes/actions.
  • TypeScript: built for strict mode; you can infer user/session types from your auth instance.

Why this is a game‑changer

  • One mental model across frameworks—move code without rewriting auth.
  • Batteries included, yet extensible—most teams can delete custom auth glue.
  • Own your users and your costs—no vendor lock‑in, no per‑MAU fees.

If you’re starting a new app or migrating from Auth0/Clerk/NextAuth, Better Auth gives you control, performance, and a simpler developer experience.


References & further reading

  • Installation, handlers, and plugins: better-auth.com (see docs for your framework)
  • Examples: Next.js, Nuxt, Remix, SvelteKit, Astro
  • Core concepts: sessions, cookies, client hooks, API endpoints, plugins

Want a follow‑up? I can publish a step‑by‑step Next.js starter with email+password, GitHub SSO, and 2FA enabled, plus route protection and middleware.

Read more