Better Auth
Framework-agnostic TypeScript auth library. Plugin-based architecture, 40+ OAuth providers, 18+ framework integrations.
Quick Start
Install
npm install 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/expo |
React Native/Expo |
Environment Variables
BETTER_AUTH_SECRET=<32+ chars, generate: openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=<connection string>
Server Config (lib/auth.ts)
import { betterAuth } from "better-auth";
export const auth = betterAuth({
database: process.env.DATABASE_URL, // or adapter instance
emailAndPassword: { enabled: true },
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
plugins: [], // add plugins here
});
export type Session = typeof auth.$Infer.Session;
Client Config (lib/auth-client.ts)
import { createAuthClient } from "better-auth/react"; // or /vue, /svelte, /solid, /client
export const authClient = createAuthClient({
plugins: [], // add client plugins here
});
Route Handler
| 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 |
app.all("/api/auth/*splat", toNodeHandler(auth)) |
| Hono |
route |
app.on(["POST","GET"], "/api/auth/**", (c) => auth.handler(c.req.raw)) |
| SvelteKit |
hooks.server.ts |
svelteKitHandler({ auth, event }) |
| Astro |
pages/api/auth/[...all].ts |
toAstroHandler(auth) |
| Elysia |
plugin |
new Elysia().mount(auth.handler) |
See references/framework-integrations.md for all frameworks.
CLI Commands
npx @better-auth/cli@latest migrate # Apply schema (built-in adapter)
npx @better-auth/cli@latest generate # Generate for Prisma/Drizzle
npx @better-auth/cli@latest generate --output prisma/schema.prisma
npx @better-auth/cli@latest generate --output src/db/auth-schema.ts
Re-run after adding/changing plugins.
Core Concepts
- Server instance (
auth): handles all auth logic, DB, sessions
- Client instance (
authClient): framework-specific hooks (useSession, signIn, signUp, signOut)
- Plugins: extend both server and client — add endpoints, DB tables, hooks
- Type inference:
auth.$Infer.Session, auth.$Infer.Session.user for full type safety
- For separate client/server projects:
createAuthClient<typeof auth>()
Authentication Methods
| Method |
Package |
Config/Plugin |
Reference |
| Email/Password |
built-in |
emailAndPassword: { enabled: true } |
authentication.md |
| Social OAuth |
built-in |
socialProviders: { google: {...} } |
authentication.md |
| Magic Link |
built-in |
magicLink() plugin |
authentication.md |
| Passkey |
@better-auth/passkey |
passkey() plugin |
authentication.md |
| Username |
built-in |
username() plugin |
authentication.md |
| Email OTP |
built-in |
emailOtp() plugin |
authentication.md |
| Phone Number |
built-in |
phoneNumber() plugin |
authentication.md |
| Anonymous |
built-in |
anonymous() plugin |
authentication.md |
Plugin Quick Reference
Import from dedicated paths for tree-shaking: import { twoFactor } from "better-auth/plugins/two-factor" NOT from "better-auth/plugins".
| Plugin |
Server Import |
Client Import |
Purpose |
twoFactor |
better-auth/plugins/two-factor |
twoFactorClient |
TOTP, OTP, backup codes |
organization |
better-auth/plugins/organization |
organizationClient |
Multi-tenant orgs, teams, RBAC |
admin |
better-auth/plugins/admin |
adminClient |
User management, impersonation |
passkey |
@better-auth/passkey |
passkeyClient |
WebAuthn/FIDO2 |
magicLink |
better-auth/plugins/magic-link |
magicLinkClient |
Passwordless email links |
emailOtp |
better-auth/plugins/email-otp |
emailOtpClient |
Email one-time passwords |
username |
better-auth/plugins/username |
usernameClient |
Username-based auth |
phoneNumber |
better-auth/plugins/phone-number |
phoneNumberClient |
Phone-based auth |
anonymous |
better-auth/plugins/anonymous |
anonymousClient |
Guest sessions |
apiKey |
better-auth/plugins/api-key |
apiKeyClient |
API key management |
bearer |
better-auth/plugins/bearer |
— |
Bearer token auth |
jwt |
better-auth/plugins/jwt |
jwtClient |
JWT tokens |
multiSession |
better-auth/plugins/multi-session |
multiSessionClient |
Multiple active sessions |
oauthProvider |
better-auth/plugins/oauth-provider |
— |
Become OAuth provider |
oidcProvider |
better-auth/plugins/oidc-provider |
— |
Become OIDC provider |
sso |
@better-auth/sso |
ssoClient |
SAML/OIDC enterprise SSO |
openAPI |
better-auth/plugins/open-api |
— |
API documentation |
customSession |
better-auth/plugins/custom-session |
— |
Extend session data |
genericOAuth |
better-auth/plugins/generic-oauth |
genericOAuthClient |
Custom OAuth providers |
oneTap |
better-auth/plugins/one-tap |
oneTapClient |
Google One Tap |
Pattern: server plugin in auth({ plugins: [...] }) + client plugin in createAuthClient({ plugins: [...] }) + re-run CLI migrations.
See references/plugins.md for detailed usage and custom plugin creation.
Database Setup
| Adapter |
Setup |
| SQLite |
Pass better-sqlite3 or bun:sqlite instance |
| PostgreSQL |
Pass pg.Pool instance |
| MySQL |
Pass mysql2 pool |
| 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 |
| Connection string |
database: process.env.DATABASE_URL (uses built-in Kysely) |
Critical: Config uses ORM model name, NOT DB table name. Prisma model User mapping to table users → use modelName: "user".
Core schema tables: user, session, account, verification. Plugins add their own tables.
See references/setup.md for full database setup details.
Session Management
Key options:
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days (default)
updateAge: 60 * 60 * 24, // refresh every 24h (default)
freshAge: 60 * 60 * 24, // require re-auth after 24h for sensitive ops
cookieCache: {
enabled: true,
maxAge: 300, // 5 min
strategy: "compact", // "compact" | "jwt" | "jwe"
},
}
secondaryStorage (Redis/KV): sessions go there by default, not DB
- Stateless mode: no DB + cookieCache = session in cookie only
customSession plugin: extend session with custom fields
See references/sessions.md for full session management details.
Security Checklist
| DO |
DON'T |
| Use 32+ char secret with high entropy |
Commit secrets to version control |
Set baseURL with HTTPS in production |
Disable CSRF check (disableCSRFCheck) |
Configure trustedOrigins for all frontends |
Disable origin check |
| Enable rate limiting (on by default in prod) |
Use "memory" rate limit storage in serverless |
Configure backgroundTasks.handler on serverless |
Skip email verification setup |
Use "jwe" cookie cache for sensitive session data |
Store OAuth tokens unencrypted if used for API calls |
Set revokeSessionsOnPasswordReset: true |
Return specific error messages ("user not found") |
See references/security.md for complete security hardening guide.
Common Gotchas
- Model vs table name — config uses ORM model name, not DB table name
- Plugin schema — re-run CLI after adding/changing plugins
- Secondary storage — sessions go there by default, not DB. Set
session.storeSessionInDatabase: true to persist both
- Cookie cache — custom session fields NOT cached, always re-fetched from DB
- Callback URLs — always use absolute URLs with origin (not relative paths)
- Express v5 — use
"/api/auth/*splat" not "/api/auth/*" for catch-all routes
- Next.js RSC — add
nextCookies() plugin to auth config for server component session access
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 match exactly |
| Type errors after adding plugin |
Re-run CLI generate/migrate |
| Session null in RSC |
Add nextCookies() plugin |
| 2FA redirect not working |
Add twoFactorClient with onTwoFactorRedirect to client |
Reference Index
| File |
When to read |
| setup.md |
Setting up new project, configuring DB, route handlers |
| authentication.md |
Implementing any auth method (email, social, passkey, magic link, etc.) |
| sessions.md |
Configuring session expiry, caching, stateless mode, secondary storage |
| security.md |
Hardening for production — rate limiting, CSRF, cookies, OAuth security |
| plugins.md |
Using or creating plugins, plugin catalog |
| framework-integrations.md |
Framework-specific setup (Next.js, Nuxt, SvelteKit, Hono, Express, etc.) |
| two-factor.md |
Implementing 2FA (TOTP, OTP, backup codes, trusted devices) |
| organizations.md |
Multi-tenant orgs, teams, invitations, RBAC |
| admin.md |
User management, roles, banning, impersonation |
| hooks-and-middleware.md |
Custom logic via before/after hooks, DB hooks, middleware |
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: fellipeutaka-leon-better-auth3description: Better Auth4---56# Better Auth78Framework-agnostic TypeScript auth library. Plugin-based architecture, 40+ OAuth providers, 18+ framework integrations.910## Quick Start1112### Install1314```bash15npm install better-auth16```1718Scoped packages (as needed):1920| Package | Use case |21|---------|----------|22| `@better-auth/passkey` | WebAuthn/Passkey auth |23| `@better-auth/sso` | SAML/OIDC enterprise SSO |24| `@better-auth/stripe` | Stripe payments |25| `@better-auth/expo` | React Native/Expo |2627### Environment Variables2829```env30BETTER_AUTH_SECRET=<32+ chars, generate: openssl rand -base64 32>31BETTER_AUTH_URL=http://localhost:300032DATABASE_URL=<connection string>33```3435### Server Config (`lib/auth.ts`)3637```ts38import { betterAuth } from "better-auth";3940export const auth = betterAuth({41 database: process.env.DATABASE_URL, // or adapter instance42 emailAndPassword: { enabled: true },43 socialProviders: {44 google: {45 clientId: process.env.GOOGLE_CLIENT_ID!,46 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,47 },48 },49 plugins: [], // add plugins here50});5152export type Session = typeof auth.$Infer.Session;53```5455### Client Config (`lib/auth-client.ts`)5657```ts58import { createAuthClient } from "better-auth/react"; // or /vue, /svelte, /solid, /client5960export const authClient = createAuthClient({61 plugins: [], // add client plugins here62});63```6465### Route Handler6667| Framework | File | Handler |68|-----------|------|---------|69| Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` |70| Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export |71| Express | any | `app.all("/api/auth/*splat", toNodeHandler(auth))` |72| Hono | route | `app.on(["POST","GET"], "/api/auth/**", (c) => auth.handler(c.req.raw))` |73| SvelteKit | `hooks.server.ts` | `svelteKitHandler({ auth, event })` |74| Astro | `pages/api/auth/[...all].ts` | `toAstroHandler(auth)` |75| Elysia | plugin | `new Elysia().mount(auth.handler)` |7677See [references/framework-integrations.md](references/framework-integrations.md) for all frameworks.7879### CLI Commands8081```bash82npx @better-auth/cli@latest migrate # Apply schema (built-in adapter)83npx @better-auth/cli@latest generate # Generate for Prisma/Drizzle84npx @better-auth/cli@latest generate --output prisma/schema.prisma85npx @better-auth/cli@latest generate --output src/db/auth-schema.ts86```8788**Re-run after adding/changing plugins.**8990## Core Concepts9192- **Server instance** (`auth`): handles all auth logic, DB, sessions93- **Client instance** (`authClient`): framework-specific hooks (`useSession`, `signIn`, `signUp`, `signOut`)94- **Plugins**: extend both server and client — add endpoints, DB tables, hooks95- **Type inference**: `auth.$Infer.Session`, `auth.$Infer.Session.user` for full type safety96- For separate client/server projects: `createAuthClient<typeof auth>()`9798## Authentication Methods99100| Method | Package | Config/Plugin | Reference |101|--------|---------|---------------|-----------|102| Email/Password | built-in | `emailAndPassword: { enabled: true }` | [authentication.md](references/authentication.md) |103| Social OAuth | built-in | `socialProviders: { google: {...} }` | [authentication.md](references/authentication.md) |104| Magic Link | built-in | `magicLink()` plugin | [authentication.md](references/authentication.md) |105| Passkey | `@better-auth/passkey` | `passkey()` plugin | [authentication.md](references/authentication.md) |106| Username | built-in | `username()` plugin | [authentication.md](references/authentication.md) |107| Email OTP | built-in | `emailOtp()` plugin | [authentication.md](references/authentication.md) |108| Phone Number | built-in | `phoneNumber()` plugin | [authentication.md](references/authentication.md) |109| Anonymous | built-in | `anonymous()` plugin | [authentication.md](references/authentication.md) |110111## Plugin Quick Reference112113Import from dedicated paths for tree-shaking: `import { twoFactor } from "better-auth/plugins/two-factor"` NOT `from "better-auth/plugins"`.114115| Plugin | Server Import | Client Import | Purpose |116|--------|---------------|---------------|---------|117| `twoFactor` | `better-auth/plugins/two-factor` | `twoFactorClient` | TOTP, OTP, backup codes |118| `organization` | `better-auth/plugins/organization` | `organizationClient` | Multi-tenant orgs, teams, RBAC |119| `admin` | `better-auth/plugins/admin` | `adminClient` | User management, impersonation |120| `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn/FIDO2 |121| `magicLink` | `better-auth/plugins/magic-link` | `magicLinkClient` | Passwordless email links |122| `emailOtp` | `better-auth/plugins/email-otp` | `emailOtpClient` | Email one-time passwords |123| `username` | `better-auth/plugins/username` | `usernameClient` | Username-based auth |124| `phoneNumber` | `better-auth/plugins/phone-number` | `phoneNumberClient` | Phone-based auth |125| `anonymous` | `better-auth/plugins/anonymous` | `anonymousClient` | Guest sessions |126| `apiKey` | `better-auth/plugins/api-key` | `apiKeyClient` | API key management |127| `bearer` | `better-auth/plugins/bearer` | — | Bearer token auth |128| `jwt` | `better-auth/plugins/jwt` | `jwtClient` | JWT tokens |129| `multiSession` | `better-auth/plugins/multi-session` | `multiSessionClient` | Multiple active sessions |130| `oauthProvider` | `better-auth/plugins/oauth-provider` | — | Become OAuth provider |131| `oidcProvider` | `better-auth/plugins/oidc-provider` | — | Become OIDC provider |132| `sso` | `@better-auth/sso` | `ssoClient` | SAML/OIDC enterprise SSO |133| `openAPI` | `better-auth/plugins/open-api` | — | API documentation |134| `customSession` | `better-auth/plugins/custom-session` | — | Extend session data |135| `genericOAuth` | `better-auth/plugins/generic-oauth` | `genericOAuthClient` | Custom OAuth providers |136| `oneTap` | `better-auth/plugins/one-tap` | `oneTapClient` | Google One Tap |137138Pattern: server plugin in `auth({ plugins: [...] })` + client plugin in `createAuthClient({ plugins: [...] })` + re-run CLI migrations.139140See [references/plugins.md](references/plugins.md) for detailed usage and custom plugin creation.141142## Database Setup143144| Adapter | Setup |145|---------|-------|146| SQLite | Pass `better-sqlite3` or `bun:sqlite` instance |147| PostgreSQL | Pass `pg.Pool` instance |148| MySQL | Pass `mysql2` pool |149| Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` |150| Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` |151| MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` |152| Connection string | `database: process.env.DATABASE_URL` (uses built-in Kysely) |153154**Critical:** Config uses ORM model name, NOT DB table name. Prisma model `User` mapping to table `users` → use `modelName: "user"`.155156Core schema tables: `user`, `session`, `account`, `verification`. Plugins add their own tables.157158See [references/setup.md](references/setup.md) for full database setup details.159160## Session Management161162Key options:163164```ts165session: {166 expiresIn: 60 * 60 * 24 * 7, // 7 days (default)167 updateAge: 60 * 60 * 24, // refresh every 24h (default)168 freshAge: 60 * 60 * 24, // require re-auth after 24h for sensitive ops169 cookieCache: {170 enabled: true,171 maxAge: 300, // 5 min172 strategy: "compact", // "compact" | "jwt" | "jwe"173 },174}175```176177- **`secondaryStorage`** (Redis/KV): sessions go there by default, not DB178- **Stateless mode**: no DB + cookieCache = session in cookie only179- **`customSession` plugin**: extend session with custom fields180181See [references/sessions.md](references/sessions.md) for full session management details.182183## Security Checklist184185| DO | DON'T |186|----|-------|187| Use 32+ char secret with high entropy | Commit secrets to version control |188| Set `baseURL` with HTTPS in production | Disable CSRF check (`disableCSRFCheck`) |189| Configure `trustedOrigins` for all frontends | Disable origin check |190| Enable rate limiting (on by default in prod) | Use `"memory"` rate limit storage in serverless |191| Configure `backgroundTasks.handler` on serverless | Skip email verification setup |192| Use `"jwe"` cookie cache for sensitive session data | Store OAuth tokens unencrypted if used for API calls |193| Set `revokeSessionsOnPasswordReset: true` | Return specific error messages ("user not found") |194195See [references/security.md](references/security.md) for complete security hardening guide.196197## Common Gotchas1981991. **Model vs table name** — config uses ORM model name, not DB table name2002. **Plugin schema** — re-run CLI after adding/changing plugins2013. **Secondary storage** — sessions go there by default, not DB. Set `session.storeSessionInDatabase: true` to persist both2024. **Cookie cache** — custom session fields NOT cached, always re-fetched from DB2035. **Callback URLs** — always use absolute URLs with origin (not relative paths)2046. **Express v5** — use `"/api/auth/*splat"` not `"/api/auth/*"` for catch-all routes2057. **Next.js RSC** — add `nextCookies()` plugin to auth config for server component session access206207## Troubleshooting208209| Issue | Fix |210|-------|-----|211| "Secret not set" | Add `BETTER_AUTH_SECRET` env var |212| "Invalid Origin" | Add domain to `trustedOrigins` |213| Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod |214| OAuth callback errors | Verify redirect URIs in provider dashboard match exactly |215| Type errors after adding plugin | Re-run CLI generate/migrate |216| Session null in RSC | Add `nextCookies()` plugin |217| 2FA redirect not working | Add `twoFactorClient` with `onTwoFactorRedirect` to client |218219## Reference Index220221| File | When to read |222|------|-------------|223| [setup.md](references/setup.md) | Setting up new project, configuring DB, route handlers |224| [authentication.md](references/authentication.md) | Implementing any auth method (email, social, passkey, magic link, etc.) |225| [sessions.md](references/sessions.md) | Configuring session expiry, caching, stateless mode, secondary storage |226| [security.md](references/security.md) | Hardening for production — rate limiting, CSRF, cookies, OAuth security |227| [plugins.md](references/plugins.md) | Using or creating plugins, plugin catalog |228| [framework-integrations.md](references/framework-integrations.md) | Framework-specific setup (Next.js, Nuxt, SvelteKit, Hono, Express, etc.) |229| [two-factor.md](references/two-factor.md) | Implementing 2FA (TOTP, OTP, backup codes, trusted devices) |230| [organizations.md](references/organizations.md) | Multi-tenant orgs, teams, invitations, RBAC |231| [admin.md](references/admin.md) | User management, roles, banning, impersonation |232| [hooks-and-middleware.md](references/hooks-and-middleware.md) | Custom logic via before/after hooks, DB hooks, middleware |233234---235> Converted and distributed by [TomeVault](https://tomevault.io/claim/fellipeutaka) — claim your Tome and manage your conversions.236<!-- tomevault:4.0:skill_md:2026-04-13 -->