claude/skills/better-auth/SKILL.md
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.
npx skillsauth add einverne/dotfiles better-authInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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-auth
Create .env file:
BETTER_AUTH_SECRET=<generated-secret-key>
BETTER_AUTH_URL=http://localhost:3000
Generate 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 migrate
Create 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: "[email protected]",
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: "[email protected]",
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: "[email protected]",
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 generate
Manual 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: "[email protected]",
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 packagedevelopment
生成符合项目规范的 React 组件。当用户要求创建组件、新建 React 组件或生成组件文件时使用
development
生成符合 Conventional Commits 规范的 Git 提交信息。当用户要求生成提交、创建 commit 或写提交信息时使用
devops
将当前分支部署到测试环境。当用户要求部署、发布到测试或在 staging 环境测试时使用
development
进行系统化的代码审查,检查代码质量、安全性和性能。当用户要求审查代码、review 或检查代码时使用