Better auth best practices
Skill escwxyz/skills-re/.agents/skills/better-auth-best-practices
Code for SKILLS.re
npx -y skills add escwxyz/skills-re --skill better-auth-best-practicesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
6.7 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
Better 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:
BETTER_AUTH_SECRETandBETTER_AUTH_URL - Create
auth.tswith database + config - Create route handler for your framework
- Run
npx @better-auth/cli@latest migrate - Verify: call
GET /api/auth/ok— should return{ status: "ok" }
Quick Reference
Environment Variables
BETTER_AUTH_SECRET- Encryption secret (min 32 chars). Generate:openssl rand -base64 32BETTER_AUTH_URL- Base URL (e.g.,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
npx @better-auth/cli@latest migrate- Apply schema (built-in adapter)npx @better-auth/cli@latest generate- Generate schema for Prisma/Drizzlenpx @better-auth/cli mcp --cursor- Add MCP to AI tools
Re-run after adding/changing plugins.
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 |
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
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
Security
In advanced:
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").
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
Resources
Gives 0 of the 12 instructions most auth identity skills give in ~1.6k tokens
Counted across 409 of the 410 authors here whose files we hold, read 2026-08-06
- hash passwords with bcrypt or argon2in 53 of 409, across 43 files
- use parameterized queriesin 47 of 409, across 39 files
- load SECRET_KEY from environment variablesin 23 of 409, across 14 files
- validate all input server-sidein 19 of 409, across 11 files
- refresh access tokens before expiryin 17 of 409, across 9 files
- store tokens in httponly cookiesin 17 of 409, across 16 files
- store refresh tokens securelyin 16 of 409, across 6 files
- validate webhook signatures before processingin 15 of 409, across 5 files
- sanitize user inputsin 15 of 409, across 9 files
- implement rate limiting on auth endpointsin 14 of 409, across 9 files
- encrypt sensitive data at restin 13 of 409, across 10 files
- validate uploaded file extensions and sizesin 12 of 409, across 5 files
Said here and by no other author read
- consult official better auth documentation
- re-run cli after adding plugins
- use adapter model names over table names
- import plugins from dedicated module paths
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.