Marketplace better-auth-best-practices
Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration.
git clone https://github.com/aiskillstore/marketplace
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/better-auth/better-auth-best-practices" ~/.claude/skills/aiskillstore-marketplace-better-auth-best-practices && rm -rf "$T"
skills/better-auth/better-auth-best-practices/SKILL.mdBetter Auth Integration Guide
Always consult better-auth.com/docs for code examples and latest API.
Setup Workflow
- Install:
npm install better-auth - Set env vars:
andBETTER_AUTH_SECRETBETTER_AUTH_URL - Create
with database + configauth.ts - Create route handler for your framework
- Run
npx @better-auth/cli@latest migrate - Verify: call
— should returnGET /api/auth/ok{ status: "ok" }
Quick Reference
Environment Variables
- Encryption secret (min 32 chars). Generate:BETTER_AUTH_SECRETopenssl rand -base64 32
- Base URL (e.g.,BETTER_AUTH_URL
)https://example.com
Only define
baseURL/secret in config if env vars are NOT set.
File Location
CLI looks for
auth.ts in: ./, ./lib, ./utils, or under ./src. Use --config for custom path.
CLI Commands
- Apply schema (built-in adapter)npx @better-auth/cli@latest migrate
- Generate schema for Prisma/Drizzlenpx @better-auth/cli@latest generate
- Add MCP to AI toolsnpx @better-auth/cli mcp --cursor
Re-run after adding/changing plugins.
Core Config Options
| Option | Notes |
|---|---|
| Optional display name |
| Only if not set |
| Default . Set for root. |
| Only if not set |
| Required for most features. See adapters docs. |
| Redis/KV for sessions & rate limits |
| to activate |
| |
| Array of plugins |
| CSRF whitelist |
Database
Direct connections: Pass
pg.Pool, mysql2 pool, better-sqlite3, or bun:sqlite instance.
ORM adapters: Import from
better-auth/adapters/drizzle, better-auth/adapters/prisma, 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".
Session Management
Storage priority:
- If
defined → sessions go there (not DB)secondaryStorage - Set
to also persist to DBsession.storeSessionInDatabase: true - No database +
→ fully stateless modecookieCache
Cookie cache strategies:
(default) - Base64url + HMAC. Smallest.compact
- Standard JWT. Readable but signed.jwt
- Encrypted. Maximum security.jwe
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
- Must be defined for verification to workemailVerification.sendVerificationEmail
/emailVerification.sendOnSignUp
- Auto-send triggerssendOnSignIn
- Password reset email handleremailAndPassword.sendResetPassword
Security
In
:advanced
- Force HTTPS cookiesuseSecureCookies
- ⚠️ Security riskdisableCSRFCheck
- ⚠️ Security riskdisableOriginCheck
- Share cookies across subdomainscrossSubDomainCookies.enabled
- Custom IP headers for proxiesipAddress.ipAddressHeaders
- Custom ID generation ordatabase.generateId
/"serial"
/"uuid"false
Rate limiting:
rateLimit.enabled, rateLimit.window, rateLimit.max, rateLimit.storage ("memory" | "database" | "secondary-storage").
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.
Plugins
Import from dedicated paths for tree-shaking:
import { twoFactor } from "better-auth/plugins/two-factor"
NOT
from "better-auth/plugins".
Popular plugins:
twoFactor, organization, passkey, magicLink, emailOtp, username, phoneNumber, admin, apiKey, bearer, jwt, multiSession, sso, oauthProvider, oidcProvider, openAPI, genericOAuth.
Client plugins go in
createAuthClient({ plugins: [...] }).
Client
Import from:
better-auth/client (vanilla), better-auth/react, better-auth/vue, better-auth/svelte, better-auth/solid.
Key methods:
signUp.email(), signIn.email(), signIn.social(), signOut(), useSession(), getSession(), revokeSession(), revokeSessions().
Type Safety
Infer types:
typeof auth.$Infer.Session, typeof auth.$Infer.Session.user.
For separate client/server projects:
createAuthClient<typeof auth>().
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