Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Security hardening best practices for Better Auth: session security, CSRF, rate limiting, and token hygiene.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: better-auth-best-practices3description: 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.4---56# Better Auth Integration Guide78**Always consult [better-auth.com/docs](https://better-auth.com/docs) for code examples and latest API.**910---1112## Setup Workflow13141. Install: `npm install better-auth`152. Set env vars: `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL`163. Create `auth.ts` with database + config174. Create route handler for your framework185. Run `npx @better-auth/cli@latest migrate`196. Verify: call `GET /api/auth/ok` — should return `{ status: "ok" }`2021---2223## Quick Reference2425### Environment Variables26- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`27- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)2829Only define `baseURL`/`secret` in config if env vars are NOT set.3031### File Location32CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.3334### CLI Commands35- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)36- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle37- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools3839**Re-run after adding/changing plugins.**4041---4243## Core Config Options4445| Option | Notes |46|--------|-------|47| `appName` | Optional display name |48| `baseURL` | Only if `BETTER_AUTH_URL` not set |49| `basePath` | Default `/api/auth`. Set `/` for root. |50| `secret` | Only if `BETTER_AUTH_SECRET` not set |51| `database` | Required for most features. See adapters docs. |52| `secondaryStorage` | Redis/KV for sessions & rate limits |53| `emailAndPassword` | `{ enabled: true }` to activate |54| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |55| `plugins` | Array of plugins |56| `trustedOrigins` | CSRF whitelist |5758---5960## Database6162**Direct connections:** Pass `pg.Pool`, `mysql2` pool, `better-sqlite3`, or `bun:sqlite` instance.6364**ORM adapters:** Import from `better-auth/adapters/drizzle`, `better-auth/adapters/prisma`, `better-auth/adapters/mongodb`.6566**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"`.6768---6970## Session Management7172**Storage priority:**731. If `secondaryStorage` defined → sessions go there (not DB)742. Set `session.storeSessionInDatabase: true` to also persist to DB753. No database + `cookieCache` → fully stateless mode7677**Cookie cache strategies:**78- `compact` (default) - Base64url + HMAC. Smallest.79- `jwt` - Standard JWT. Readable but signed.80- `jwe` - Encrypted. Maximum security.8182**Key options:** `session.expiresIn` (default 7 days), `session.updateAge` (refresh interval), `session.cookieCache.maxAge`, `session.cookieCache.version` (change to invalidate all sessions).8384---8586## User & Account Config8788**User:** `user.modelName`, `user.fields` (column mapping), `user.additionalFields`, `user.changeEmail.enabled` (disabled by default), `user.deleteUser.enabled` (disabled by default).8990**Account:** `account.modelName`, `account.accountLinking.enabled`, `account.storeAccountCookie` (for stateless OAuth).9192**Required for registration:** `email` and `name` fields.9394---9596## Email Flows9798- `emailVerification.sendVerificationEmail` - Must be defined for verification to work99- `emailVerification.sendOnSignUp` / `sendOnSignIn` - Auto-send triggers100- `emailAndPassword.sendResetPassword` - Password reset email handler101102---103104## Security105106**In `advanced`:**107- `useSecureCookies` - Force HTTPS cookies108- `disableCSRFCheck` - ⚠️ Security risk109- `disableOriginCheck` - ⚠️ Security risk110- `crossSubDomainCookies.enabled` - Share cookies across subdomains111- `ipAddress.ipAddressHeaders` - Custom IP headers for proxies112- `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false`113114**Rate limiting:** `rateLimit.enabled`, `rateLimit.window`, `rateLimit.max`, `rateLimit.storage` ("memory" | "database" | "secondary-storage").115116---117118## Hooks119120**Endpoint hooks:** `hooks.before` / `hooks.after` - Array of `{ matcher, handler }`. Use `createAuthMiddleware`. Access `ctx.path`, `ctx.context.returned` (after), `ctx.context.session`.121122**Database hooks:** `databaseHooks.user.create.before/after`, same for `session`, `account`. Useful for adding default values or post-creation actions.123124**Hook context (`ctx.context`):** `session`, `secret`, `authCookies`, `password.hash()`/`verify()`, `adapter`, `internalAdapter`, `generateId()`, `tables`, `baseURL`.125126---127128## Plugins129130**Import from dedicated paths for tree-shaking:**131```132import { twoFactor } from "better-auth/plugins/two-factor"133```134NOT `from "better-auth/plugins"`.135136**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.137138Client plugins go in `createAuthClient({ plugins: [...] })`.139140---141142## Client143144Import from: `better-auth/client` (vanilla), `better-auth/react`, `better-auth/vue`, `better-auth/svelte`, `better-auth/solid`.145146Key methods: `signUp.email()`, `signIn.email()`, `signIn.social()`, `signOut()`, `useSession()`, `getSession()`, `revokeSession()`, `revokeSessions()`.147148---149150## Type Safety151152Infer types: `typeof auth.$Infer.Session`, `typeof auth.$Infer.Session.user`.153154For separate client/server projects: `createAuthClient<typeof auth>()`.155156---157158## Common Gotchas1591601. **Model vs table name** - Config uses ORM model name, not DB table name1612. **Plugin schema** - Re-run CLI after adding plugins1623. **Secondary storage** - Sessions go there by default, not DB1634. **Cookie cache** - Custom session fields NOT cached, always re-fetched1645. **Stateless mode** - No DB = session in cookie only, logout on cache expiry1656. **Change email flow** - Sends to current email first, then new email166167---168169## Resources170171- [Docs](https://better-auth.com/docs)172- [Options Reference](https://better-auth.com/docs/reference/options)173- [LLMs.txt](https://better-auth.com/llms.txt)174- [GitHub](https://github.com/better-auth/better-auth)175- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)