Guide for implementing Better Auth - a framework-agnostic authentication and authorization framework for TypeScript. Use when adding authentication features like email/password, OAuth, 2FA, passkeys, or advanced auth functionality to applications.
84
Quality
83%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Better Auth is a comprehensive, framework-agnostic authentication and authorization framework for TypeScript that provides built-in support for email/password authentication, social sign-on, and a powerful plugin ecosystem for advanced features.
Use this skill when:
Better Auth follows a client-server architecture:
better-auth): Handles auth logic, database operations, and API routesbetter-auth/client): Provides hooks and methods for authenticationnpm install better-auth
# or
pnpm add better-auth
# or
yarn add better-auth
# or
bun add better-authCreate .env file:
BETTER_AUTH_SECRET=<generated-secret-key>
BETTER_AUTH_URL=http://localhost:3000Generate secret: Use openssl or a random string generator (min 32 characters).
Create auth.ts in project root, lib/, utils/, or nested under src/, app/, or server/:
import { betterAuth } from "better-auth";
export const auth = betterAuth({
database: {
// Database configuration
},
emailAndPassword: {
enabled: true,
autoSignIn: true // Users auto sign-in after signup
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}
}
});Choose your database setup:
Direct Database Connection:
import { betterAuth } from "better-auth";
import Database from "better-sqlite3";
// or import { Pool } from "pg";
// or import { createPool } from "mysql2/promise";
export const auth = betterAuth({
database: new Database("./sqlite.db"),
// or: new Pool({ connectionString: process.env.DATABASE_URL })
// or: createPool({ host: "localhost", user: "root", ... })
});ORM Adapter:
// Drizzle
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql", "sqlite"
}),
});
// Prisma
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
});
// MongoDB
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { client } from "@/db";
export const auth = betterAuth({
database: mongodbAdapter(client),
});Use Better Auth CLI:
# Generate schema/migration files
npx @better-auth/cli generate
# Or migrate directly (Kysely adapter only)
npx @better-auth/cli migrateCreate catch-all route for /api/auth/*:
Next.js (App Router):
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);Nuxt:
// server/api/auth/[...all].ts
import { auth } from "~/utils/auth";
export default defineEventHandler((event) => {
return auth.handler(toWebRequest(event));
});SvelteKit:
// hooks.server.ts
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}Hono:
import { Hono } from "hono";
import { auth } from "./auth";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw));Express:
import express from "express";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./auth";
const app = express();
app.all("/api/auth/*", toNodeHandler(auth));Create auth-client.ts:
import { createAuthClient } from "better-auth/client";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"
});Server Configuration:
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
autoSignIn: true, // default: true
}
});Client Usage:
// Sign Up
const { data, error } = await authClient.signUp.email({
email: "user@example.com",
password: "securePassword123",
name: "John Doe",
image: "https://example.com/avatar.jpg", // optional
callbackURL: "/dashboard" // optional
}, {
onSuccess: (ctx) => {
// redirect or show success
},
onError: (ctx) => {
alert(ctx.error.message);
}
});
// Sign In
const { data, error } = await authClient.signIn.email({
email: "user@example.com",
password: "securePassword123",
callbackURL: "/dashboard",
rememberMe: true // default: true
});Server Configuration:
export const auth = betterAuth({
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
// Other providers: apple, discord, facebook, etc.
}
});Client Usage:
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard",
errorCallbackURL: "/error",
newUserCallbackURL: "/welcome",
});await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
}
}
});Using Hooks (React/Vue/Svelte/Solid):
// React
import { authClient } from "@/lib/auth-client";
export function UserProfile() {
const { data: session, isPending, error } = authClient.useSession();
if (isPending) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Welcome, {session?.user.name}!</div>;
}
// Vue
<script setup>
import { authClient } from "~/lib/auth-client";
const session = authClient.useSession();
</script>
<template>
<div v-if="session.data">{{ session.data.user.email }}</div>
</template>
// Svelte
<script>
import { authClient } from "$lib/auth-client";
const session = authClient.useSession();
</script>
<p>{$session.data?.user.email}</p>Using getSession:
const { data: session, error } = await authClient.getSession();// Next.js
import { auth } from "./auth";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers()
});
// Hono
app.get("/protected", async (c) => {
const session = await auth.api.getSession({
headers: c.req.raw.headers
});
if (!session) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ user: session.user });
});Better Auth's plugin system allows adding advanced features easily.
Server-Side:
import { betterAuth } from "better-auth";
import { twoFactor, organization, username } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
twoFactor(),
organization(),
username(),
]
});Client-Side:
import { createAuthClient } from "better-auth/client";
import {
twoFactorClient,
organizationClient,
usernameClient
} from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [
twoFactorClient({
twoFactorPage: "/two-factor"
}),
organizationClient(),
usernameClient()
]
});After Adding Plugins:
# Regenerate schema
npx @better-auth/cli generate
# Apply migration
npx @better-auth/cli migrate// Server
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [twoFactor()]
});
// Client
import { twoFactorClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [
twoFactorClient({ twoFactorPage: "/two-factor" })
]
});
// Usage
await authClient.twoFactor.enable({ password: "userPassword" });
await authClient.twoFactor.verifyTOTP({
code: "123456",
trustDevice: true
});// Server
import { username } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [username()]
});
// Client
import { usernameClient } from "better-auth/client/plugins";
// Sign up with username
await authClient.signUp.username({
username: "johndoe",
password: "securePassword123",
name: "John Doe"
});import { magicLink } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
magicLink({
sendMagicLink: async ({ email, url }) => {
// Send email with magic link
await sendEmail(email, url);
}
})
]
});import { passkey } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [passkey()]
});
// Client
await authClient.passkey.register();
await authClient.passkey.signIn();import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [organization()]
});
// Client
await authClient.organization.create({
name: "Acme Corp",
slug: "acme"
});
await authClient.organization.inviteMember({
organizationId: "org-id",
email: "user@example.com",
role: "member"
});export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmail(user.email, url);
},
sendOnSignUp: true
}
});export const auth = betterAuth({
rateLimit: {
enabled: true,
window: 60, // seconds
max: 10 // requests
}
});export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days in seconds
updateAge: 60 * 60 * 24 // Update every 24 hours
}
});export const auth = betterAuth({
advanced: {
corsOptions: {
origin: ["https://example.com"],
credentials: true
}
}
});Better Auth requires these core tables:
user: User accountssession: Active sessionsaccount: OAuth provider connectionsverification: Email verification tokensAuto-generate with CLI:
npx @better-auth/cli generateManual schema available in docs: Check /docs/concepts/database#core-schema
BETTER_AUTH_URL to HTTPS URL// Next.js middleware
import { auth } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"]
};await authClient.updateUser({
name: "New Name",
image: "https://example.com/new-avatar.jpg"
});// Change password
await authClient.changePassword({
currentPassword: "oldPassword",
newPassword: "newPassword"
});
// Reset password (forgot password)
await authClient.forgetPassword({
email: "user@example.com",
redirectTo: "/reset-password"
});
await authClient.resetPassword({
token: "reset-token",
password: "newPassword"
});"Unable to find auth instance"
auth.ts is in correct location (root, lib/, utils/)auth or default exportDatabase connection errors
CORS errors
corsOptions in advanced settingsPlugin not working
When implementing Better Auth:
better-auth packageb1b2fe0
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.