git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/LeoYeAI/openclaw-master-skills/better-auth-best-practices" ~/.claude/skills/comeonoliver-skillshub-better-auth-best-practices && rm -rf "$T"
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/LeoYeAI/openclaw-master-skills/better-auth-best-practices" ~/.openclaw/skills/comeonoliver-skillshub-better-auth-best-practices && rm -rf "$T"
skills/LeoYeAI/openclaw-master-skills/better-auth-best-practices/SKILL.mdBetter Auth Integration Guide
Always consult better-auth.com/docs for code examples and latest API.
Better Auth is a TypeScript-first, framework-agnostic auth framework supporting email/password, OAuth, magic links, passkeys, and more via plugins.
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