| name | better-auth |
| description | 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. |
| license | MIT |
| version | 1.0.0 |
Better Auth Skill
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.
When to Use This Skill
Use this skill when:
- Implementing authentication in TypeScript/JavaScript applications
- Adding email/password or social OAuth authentication
- Setting up 2FA, passkeys, magic links, or other advanced auth features
- Building multi-tenant applications with organization support
- Implementing session management and user management
- Working with any framework (Next.js, Nuxt, SvelteKit, Remix, Astro, Hono, Express, etc.)
Core Concepts
Key Features
- Framework Agnostic: Works with any framework (Next.js, Nuxt, Svelte, Remix, Hono, Express, etc.)
- Built-in Auth Methods: Email/password and OAuth 2.0 social providers
- Plugin Ecosystem: Easy-to-add advanced features (2FA, passkeys, magic link, username, email OTP, organization, etc.)
- Database Flexibility: Supports SQLite, PostgreSQL, MySQL, MongoDB, and more
- ORM Support: Built-in adapters for Drizzle, Prisma, Kysely, and MongoDB
- Type Safety: Full TypeScript support with excellent type inference
- Session Management: Built-in session handling for both client and server
Architecture
Better Auth follows a client-server architecture:
- Server Instance (
better-auth): Handles auth logic, database operations, and API routes - Client Instance (
better-auth/client): Provides hooks and methods for authentication - Plugins: Extend both server and client functionality
Installation & Setup
Step 1: Install Package
npm install better-auth
# or
pnpm add better-auth
# or
yarn add better-auth
# or
bun add better-auth
Step 2: Environment Variables
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).
Step 3: Create Auth Server Instance
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!,
},
},
});
Step 4: Database Configuration
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),
});
Step 5: Create Database Schema
Use Better Auth CLI:
# Generate schema/migration files
npx @better-auth/cli generate
# Or migrate directly (Kysely adapter only)
npx @better-auth/cli migrate
Step 6: Mount API Handler
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));
Step 7: Create Client Instance
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',
});
Authentication Methods
Email & Password
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
});
Social OAuth
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',
});
Sign Out
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push('/login');
},
},
});
Session Management
Client-Side Session
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();
Server-Side Session
// 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 });
});
Plugin System
Better Auth's plugin system allows adding advanced features easily.
Using Plugins
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
Popular Plugins
Two-Factor Authentication (2FA)
// 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,
});
Username Authentication
// 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',
});
Magic Link
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);
},
}),
],
});
Passkey (WebAuthn)
import { passkey } from 'better-auth/plugins';
export const auth = betterAuth({
plugins: [passkey()],
});
// Client
await authClient.passkey.register();
await authClient.passkey.signIn();
Organization/Multi-Tenancy
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',
});
Advanced Configuration
Email Verification
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmail(user.email, url);
},
sendOnSignUp: true,
},
});
Rate Limiting
export const auth = betterAuth({
rateLimit: {
enabled: true,
window: 60, // seconds
max: 10, // requests
},
});
Custom Session Expiration
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days in seconds
updateAge: 60 * 60 * 24, // Update every 24 hours
},
});
CORS Configuration
export const auth = betterAuth({
advanced: {
corsOptions: {
origin: ['https://example.com'],
credentials: true,
},
},
});
Database Schema
Core Tables
Better Auth requires these core tables:
user: User accountssession: Active sessionsaccount: OAuth provider connectionsverification: Email verification tokens
Auto-generate with CLI:
npx @better-auth/cli generate
Manual schema available in docs: Check /docs/concepts/database#core-schema
Best Practices
- Environment Variables: Always use environment variables for secrets
- HTTPS in Production: Set
BETTER_AUTH_URLto HTTPS URL - Session Security: Use secure cookies in production
- Error Handling: Implement proper error handling on client and server
- Type Safety: Leverage TypeScript types for better DX
- Plugin Order: Some plugins depend on others, check documentation
- Database Migrations: Always run migrations after adding plugins
- Rate Limiting: Enable rate limiting for production
- Email Verification: Implement email verification for security
- Password Requirements: Customize password validation as needed
Common Patterns
Protected Routes (Server-Side)
// 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*'],
};
User Profile Updates
await authClient.updateUser({
name: 'New Name',
image: 'https://example.com/new-avatar.jpg',
});
Password Management
// 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',
});
Troubleshooting
Common Issues
"Unable to find auth instance"
- Ensure
auth.tsis in correct location (root, lib/, utils/) - Export auth instance as
author default export
- Ensure
Database connection errors
- Verify database credentials
- Check if database server is running
- Ensure correct adapter for your database
CORS errors
- Configure
corsOptionsin advanced settings - Ensure client and server URLs match
- Configure
Plugin not working
- Run migrations after adding plugins
- Check plugin is added to both server and client
- Verify plugin configuration
Framework-Specific Guides
- Next.js: Use Next.js plugin for server actions
- Nuxt: Configure server middleware
- SvelteKit: Use hooks.server.ts
- Astro: Set up API routes properly
- Hono/Express: Use appropriate node handlers
Resources
- Documentation: https://www.better-auth.com/docs
- GitHub: https://github.com/better-auth/better-auth
- Plugins: https://www.better-auth.com/docs/plugins
- Examples: https://www.better-auth.com/docs/examples
Implementation Checklist
When implementing Better Auth:
- Install
better-authpackage - Set up environment variables (SECRET, URL)
- Create auth server instance
- Configure database/adapter
- Run schema migration
- Configure authentication methods
- Mount API handler
- Create client instance
- Implement sign-up/sign-in UI
- Add session management
- Set up protected routes
- Add plugins as needed
- Test authentication flow
- Configure email sending (if needed)
- Set up error handling
- Enable rate limiting for production