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 |
1---2name: better-auth3description: Better Auth — framework-agnostic TypeScript authentication & authorization library. Covers setup, email/password, social OAuth (40+ providers), passkeys, magic links, 2FA, organizations, sessions, plugins, admin, hooks, and security hardening. Use when implementing auth with Better Auth: configuring auth instances, adding providers, setting up database adapters (Prisma, Drizzle, PostgreSQL, MySQL, SQLite, MongoDB), integrating with frameworks (Next.js, Nuxt, SvelteKit, Astro, Hono, Express, Elysia, Fastify, Expo), managing sessions, or extending with plugins.4---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 |