name: better-auth description: "TypeScript authentication framework (framework-agnostic). Features: email/password, OAuth (Google, GitHub, Discord), 2FA (TOTP, SMS), passkeys/WebAuthn, session management, RBAC, rate limiting, database adapters. Actions: implement, configure, secure authentication systems. Keywords: Better Auth, authentication, authorization, OAuth, email/password, 2FA, MFA, TOTP, passkeys, WebAuthn, session management, RBAC, rate limiting, database adapter, TypeScript auth, social login, Google auth, GitHub auth, Discord auth, email verification, password reset. Use when: implementing TypeScript auth, adding OAuth providers, setting up 2FA/MFA, managing sessions, configuring RBAC, building secure auth systems." license: MIT version: 2.0.0
Better Auth
Better Auth is a comprehensive, framework-agnostic authentication/authorization framework for TypeScript with built-in email/password, social OAuth, and powerful plugin ecosystem for advanced features.
Always consult better-auth.com/docs for code examples and latest API.
When to Use
- Implementing auth in TypeScript/JavaScript applications
- Adding email/password or social OAuth authentication
- Setting up 2FA, passkeys, magic links, advanced auth features
- Building multi-tenant apps with organization support
- Managing sessions and user lifecycle
- Working with any framework (Next.js, Nuxt, SvelteKit, Remix, Astro, Hono, Express, etc.)
Planning Phase (Before Implementation)
Before writing any code, gather requirements by scanning the project and asking the user structured questions.
Step 1: Scan the project
Analyze the codebase to auto-detect:
- Framework — Look for
next.config,svelte.config,nuxt.config,astro.config,vite.config, or Express/Hono entry files. - Database/ORM — Look for
prisma/schema.prisma,drizzle.config,package.jsondeps (pg,mysql2,better-sqlite3,mongoose,mongodb). - Existing auth — Look for existing auth libraries (
next-auth,lucia,clerk,supabase/auth,firebase/auth) inpackage.jsonor imports. - Package manager — Check for
pnpm-lock.yaml,yarn.lock,bun.lockb, orpackage-lock.json.
Use what you find to pre-fill defaults and skip questions you can already answer.
Step 2: Ask planning questions
Ask the user all applicable questions (skip any already answered by the scan):
- Project type — New project | Adding auth to existing project | Migrating from another auth library
- Framework — Next.js (App/Pages Router) | SvelteKit | Nuxt | Astro | Express | Hono | SolidStart | Other
- Database & ORM — PostgreSQL/MySQL/SQLite + Prisma/Drizzle/direct driver | MongoDB
- Authentication methods (allow multiple) — Email & password | Social OAuth | Magic link | Passkey | Phone number
- Social providers (if OAuth selected) — Google | GitHub | Apple | Microsoft | Discord | Twitter/X
- Email verification (if email/password selected) — Yes | No
- Email provider (if verification or password reset needed) — Resend | Mock (console.log)
- Features & plugins (allow multiple) — 2FA | Organizations/teams | Admin dashboard | API bearer tokens | Password reset | None
- Auth pages — Sign in | Sign up | Forgot password | Reset password | Email verification
- Auth UI style — Minimal & clean | Centered card | Split layout | Floating/glassmorphism | Custom
Step 3: Summarize and confirm the plan
Present a concise implementation plan as a markdown checklist. Get user confirmation before proceeding.
Decision Tree
Is this a new/empty project?
├─ YES → New project setup
│ 1. Identify framework
│ 2. Choose database
│ 3. Install better-auth
│ 4. Create auth.ts + auth-client.ts
│ 5. Set up route handler
│ 6. Run CLI migrate/generate
│ 7. Add features via plugins
│
├─ MIGRATING → Migration from existing auth
│ 1. Audit current auth for gaps
│ 2. Plan incremental migration
│ 3. Install better-auth alongside existing auth
│ 4. Migrate routes, then session logic, then UI
│ 5. Remove old auth library
│ 6. See migration guides in docs
│
└─ NO → Add auth to existing project
1. Analyze project structure
2. Install better-auth
3. Create auth config
4. Add route handler
5. Run schema migrations
6. Integrate into existing pages
7. Add planned plugins and features
Quick Start
Installation
npm install better-auth
# or pnpm/yarn/bun add better-auth
Scoped packages (as needed):
| Package | Use case |
|---|---|
@better-auth/passkey |
WebAuthn/Passkey auth |
@better-auth/sso |
SAML/OIDC enterprise SSO |
@better-auth/stripe |
Stripe payments |
@better-auth/scim |
SCIM user provisioning |
@better-auth/expo |
React Native/Expo |
Environment Variables
BETTER_AUTH_SECRET=<32+ chars, generate with: openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=<your database connection string>
Add OAuth secrets as needed: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GOOGLE_CLIENT_ID, etc.
Only define baseURL/secret in config if env vars are NOT set.
CLI Commands
npx @better-auth/cli@latest migrate # Apply schema (built-in adapter)
npx @better-auth/cli@latest generate # Generate schema for Prisma/Drizzle
npx @better-auth/cli mcp --cursor # Add MCP to AI tools
Re-run after adding/changing plugins.
File Location
CLI looks for auth.ts in: ./, ./lib, ./utils, or under ./src. Use --config for custom path.
Server Config (auth.ts)
Location: lib/auth.ts or src/lib/auth.ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
database: {
// See Database section below
},
emailAndPassword: {
enabled: true,
autoSignIn: true
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}
}
});
// Export types
export type Session = typeof auth.$Infer.Session;
Core Config Options
| Option | Notes |
|---|---|
appName |
Optional display name |
baseURL |
Only if BETTER_AUTH_URL not set |
basePath |
Default /api/auth. Set / for root. |
secret |
Only if BETTER_AUTH_SECRET not set |
database |
Required for most features. See adapters docs. |
secondaryStorage |
Redis/KV for sessions & rate limits |
emailAndPassword |
{ enabled: true } to activate |
socialProviders |
{ google: { clientId, clientSecret }, ... } |
plugins |
Array of plugins |
trustedOrigins |
CSRF whitelist |
Minimal config needs: database + emailAndPassword: { enabled: true }
Standard config adds: socialProviders, emailVerification.sendVerificationEmail, emailAndPassword.sendResetPassword
Full config adds: plugins, session, account.accountLinking, rateLimit
Client Config (auth-client.ts)
import { createAuthClient } from "better-auth/client";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"
});
Import by framework:
| Framework | Import |
|---|---|
| React/Next.js | better-auth/react |
| Vue | better-auth/vue |
| Svelte | better-auth/svelte |
| Solid | better-auth/solid |
| Vanilla JS | better-auth/client |
Client plugins go in createAuthClient({ plugins: [...] }).
Common exports: signIn, signUp, signOut, useSession, getSession
Route Handler Setup
| Framework | File | Handler |
|---|---|---|
| Next.js App Router | app/api/auth/[...all]/route.ts |
toNextJsHandler(auth) → export { GET, POST } |
| Next.js Pages | pages/api/auth/[...all].ts |
toNextJsHandler(auth) → default export |
| Express | Any file | app.all("/api/auth/*", toNodeHandler(auth)) |
| SvelteKit | src/hooks.server.ts |
svelteKitHandler(auth) |
| SolidStart | Route file | solidStartHandler(auth) |
| Hono | Route file | auth.handler(c.req.raw) |
Next.js Server Components: Add nextCookies() plugin to auth config.
Database
Direct Connections
Pass pg.Pool, mysql2 pool, better-sqlite3, or bun:sqlite instance directly.
ORM Adapters
Import from better-auth/adapters/drizzle, better-auth/adapters/prisma, better-auth/adapters/mongodb.
| Database | Setup |
|---|---|
| SQLite | Pass better-sqlite3 or bun:sqlite instance directly |
| PostgreSQL | Pass pg.Pool instance directly |
| MySQL | Pass mysql2 pool directly |
| Prisma | prismaAdapter(prisma, { provider: "postgresql" }) from better-auth/adapters/prisma |
| Drizzle | drizzleAdapter(db, { provider: "pg" }) from better-auth/adapters/drizzle |
| MongoDB | mongodbAdapter(db) from better-auth/adapters/mongodb |
Critical: Better Auth uses adapter model names, NOT underlying table names. If Prisma model is User mapping to table users, use modelName: "user" (Prisma reference), not "users".
Database Migrations
| Adapter | Command |
|---|---|
| Built-in Kysely | npx @better-auth/cli@latest migrate (applies directly) |
| Prisma | npx @better-auth/cli@latest generate --output prisma/schema.prisma then npx prisma migrate dev |
| Drizzle | npx @better-auth/cli@latest generate --output src/db/auth-schema.ts then npx drizzle-kit push |
Re-run after adding plugins.
Basic Usage
// Sign up
await authClient.signUp.email({
email: "user@example.com",
password: "secure123",
name: "John Doe"
});
// Sign in
await authClient.signIn.email({
email: "user@example.com",
password: "secure123"
});
// OAuth
await authClient.signIn.social({ provider: "github" });
// Session (client)
const { data: session } = authClient.useSession(); // React/Vue/Svelte
const { data: session } = await authClient.getSession(); // Vanilla JS
// Session (server)
const session = await auth.api.getSession({ headers: await headers() });
Session Management
Storage priority:
- If
secondaryStoragedefined → sessions go there (not DB) - Set
session.storeSessionInDatabase: trueto also persist to DB - No database +
cookieCache→ fully stateless mode
Cookie cache strategies:
compact(default) - Base64url + HMAC. Smallest.jwt- Standard JWT. Readable but signed.jwe- Encrypted. Maximum security.
Key options: session.expiresIn (default 7 days), session.updateAge (refresh interval), session.cookieCache.maxAge, session.cookieCache.version (change to invalidate all sessions).
User & Account Config
User: user.modelName, user.fields (column mapping), user.additionalFields, user.changeEmail.enabled (disabled by default), user.deleteUser.enabled (disabled by default).
Account: account.modelName, account.accountLinking.enabled, account.storeAccountCookie (for stateless OAuth).
Required for registration: email and name fields.
Email Flows
emailVerification.sendVerificationEmail- Must be defined for verification to workemailVerification.sendOnSignUp/sendOnSignIn- Auto-send triggersemailAndPassword.sendResetPassword- Password reset email handler
Feature Selection Matrix
| Feature | Plugin Required | Use Case | Reference |
|---|---|---|---|
| Email/Password | No (built-in) | Basic auth | email-password-auth.md |
| OAuth (GitHub, Google, etc.) | No (built-in) | Social login | oauth-providers.md |
| Email Verification | No (built-in) | Verify email addresses | email-password-auth.md |
| Password Reset | No (built-in) | Forgot password flow | email-password-auth.md |
| Two-Factor Auth (2FA/TOTP) | Yes (twoFactor) |
Enhanced security | advanced-features.md |
| Passkeys/WebAuthn | Yes (passkey) |
Passwordless auth | advanced-features.md |
| Magic Link | Yes (magicLink) |
Email-based login | advanced-features.md |
| Username Auth | Yes (username) |
Username login | email-password-auth.md |
| Organizations/Multi-tenant | Yes (organization) |
Team/org features | advanced-features.md |
| Rate Limiting | No (built-in) | Prevent abuse | advanced-features.md |
| Session Management | No (built-in) | User sessions | advanced-features.md |
Auth Method Selection Guide
Choose Email/Password when:
- Building standard web app with traditional auth
- Need full control over user credentials
- Targeting users who prefer email-based accounts
Choose OAuth when:
- Want quick signup with minimal friction
- Users already have social accounts
- Need access to social profile data
Choose Passkeys when:
- Want passwordless experience
- Targeting modern browsers/devices
- Security is top priority
Choose Magic Link when:
- Want passwordless without WebAuthn complexity
- Targeting email-first users
- Need temporary access links
Combine Multiple Methods when:
- Want flexibility for different user preferences
- Building enterprise apps with various auth requirements
- Need progressive enhancement (start simple, add more options)
Plugins
Import from dedicated paths for tree-shaking:
import { twoFactor } from "better-auth/plugins/two-factor"
NOT from "better-auth/plugins".
Common Plugins
| Plugin | Server Import | Client Import | Purpose |
|---|---|---|---|
twoFactor |
better-auth/plugins |
twoFactorClient |
2FA with TOTP/OTP |
organization |
better-auth/plugins |
organizationClient |
Teams/orgs |
admin |
better-auth/plugins |
adminClient |
User management |
bearer |
better-auth/plugins |
- | API token auth |
openAPI |
better-auth/plugins |
- | API docs |
passkey |
@better-auth/passkey |
passkeyClient |
WebAuthn |
sso |
@better-auth/sso |
- | Enterprise SSO |
Popular plugins: twoFactor, organization, passkey, magicLink, emailOtp, username, phoneNumber, admin, apiKey, bearer, jwt, multiSession, sso, oauthProvider, oidcProvider, openAPI, genericOAuth.
Plugin pattern: Server plugin + client plugin + run migrations.
Client plugins go in createAuthClient({ plugins: [...] }).
Security
Config (advanced section)
useSecureCookies- Force HTTPS cookiesdisableCSRFCheck- Security riskdisableOriginCheck- Security riskcrossSubDomainCookies.enabled- Share cookies across subdomainsipAddress.ipAddressHeaders- Custom IP headers for proxiesdatabase.generateId- Custom ID generation or"serial"/"uuid"/false
Rate limiting: rateLimit.enabled, rateLimit.window, rateLimit.max, rateLimit.storage ("memory" | "database" | "secondary-storage").
Security Checklist
-
BETTER_AUTH_SECRETset (32+ chars) -
advanced.useSecureCookies: truein production -
trustedOriginsconfigured - Rate limits enabled
- Email verification enabled
- Password reset implemented
- 2FA for sensitive apps
- CSRF protection NOT disabled
-
account.accountLinkingreviewed
Hooks
Endpoint hooks: hooks.before / hooks.after - Array of { matcher, handler }. Use createAuthMiddleware. Access ctx.path, ctx.context.returned (after), ctx.context.session.
Database hooks: databaseHooks.user.create.before/after, same for session, account. Useful for adding default values or post-creation actions.
Hook context (ctx.context): session, secret, authCookies, password.hash()/verify(), adapter, internalAdapter, generateId(), tables, baseURL.
Type Safety
Infer types: typeof auth.$Infer.Session, typeof auth.$Infer.Session.user.
For separate client/server projects: createAuthClient<typeof auth>().
Auth UI Implementation
Sign in flow:
signIn.email({ email, password })orsignIn.social({ provider, callbackURL })- Handle
errorin response - Redirect on success
Session check (client): useSession() hook returns { data: session, isPending }
Session check (server): auth.api.getSession({ headers: await headers() })
Protected routes: Check session, redirect to /sign-in if null.
Core Architecture
Better Auth uses client-server architecture:
- Server (
better-auth): Handles auth logic, database ops, API routes - Client (
better-auth/client): Provides hooks/methods for frontend - Plugins: Extend both server/client functionality
Implementation Checklist
- Install
better-authpackage - Set environment variables (SECRET, URL)
- Create auth server instance with database config
- Run schema migration (
npx @better-auth/cli generate) - Mount API handler in framework
- Create client instance
- Implement sign-up/sign-in UI
- Add session management to components
- Set up protected routes/middleware
- Add plugins as needed (regenerate schema after)
- Test complete auth flow
- Configure email sending (verification/reset)
- Enable rate limiting for production
- Set up error handling
Common Gotchas
- Model vs table name - Config uses ORM model name, not DB table name
- Plugin schema - Re-run CLI after adding plugins
- Secondary storage - Sessions go there by default, not DB
- Cookie cache - Custom session fields NOT cached, always re-fetched
- Stateless mode - No DB = session in cookie only, logout on cache expiry
- Change email flow - Sends to current email first, then new email
Troubleshooting
| Issue | Fix |
|---|---|
| "Secret not set" | Add BETTER_AUTH_SECRET env var |
| "Invalid Origin" | Add domain to trustedOrigins |
| Cookies not setting | Check baseURL matches domain; enable secure cookies in prod |
| OAuth callback errors | Verify redirect URIs in provider dashboard |
| Type errors after adding plugin | Re-run CLI generate/migrate |
Reference Documentation
Core Authentication
- Email/Password Authentication - Email/password setup, verification, password reset, username auth
- OAuth Providers - Social login setup, provider configuration, token management
- Database Integration - Database adapters, schema setup, migrations
Advanced Features
- Advanced Features - 2FA/MFA, passkeys, magic links, organizations, rate limiting, session management
Scripts
scripts/better_auth_init.py- Initialize Better Auth configuration with interactive setup
Resources
- Docs: https://better-auth.com/docs
- Options Reference: https://better-auth.com/docs/reference/options
- LLMs.txt: https://better-auth.com/llms.txt
- GitHub: https://github.com/better-auth/better-auth
- Plugins: https://better-auth.com/docs/plugins
- Examples: https://better-auth.com/docs/examples
- Init Options Source: https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts