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 akornmeier/claude-config 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:
// 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',
}),
});
// Drizzle
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { db } from '@/db';
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: 'pg', // or "mysql", "sqlite"
}),
});
// 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 packagetools
Use when translating UX specifications into build-order prompts for UI generation tools. Triggers when user has a UX spec, PRD, or detailed feature doc and needs sequential, self-contained prompts for tools like v0, Bolt, or Claude frontend-design.
development
Guide for implementing Turborepo - a high-performance build system for JavaScript and TypeScript monorepos. Use when setting up monorepos, optimizing build performance, implementing task pipelines, configuring caching strategies, or orchestrating tasks across multiple packages.
tools
Replace with description of the skill and when Claude should use it.
tools
Guide for implementing Tailwind CSS - a utility-first CSS framework for rapid UI development. Use when styling applications with responsive design, dark mode, custom themes, or building design systems with Tailwind's utility classes.