/community/better auth best practices
innernet
docsskillscommunitycompanydeveloperssecurityprivacytermscontact

a skill by better-auth, brought here by kt

better auth best practices

paste this link into your ai. it will know what to do.

/skills/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.

kindskill
last tended2026-09-25
written bybetter-auth
sourcegithub.com/better-auth/skills
kept currentwith its source · last read 2026-11-14
brought here bykt
yours?it stays under your name. to change it or take it down, write to connect@innernet.live
reading4 min

Better Auth Integration Guide

Documentation Version

Use documentation that matches the Better Auth version installed in the project. APIs and plugin names can differ across maintained release lines.

  1. 1Prefer a version explicitly named by the user.
  2. 2Otherwise, inspect the resolved better-auth version in the lockfile, falling back to the package manifest when no lockfile is available.
  3. 3When the Better Auth MCP is available, call get_doc with /llms.txt to resolve that package version to a documentation identifier. Pass the identifier to every search_docs call and pass result paths to get_doc unchanged.
  4. 4Without MCP, start at better-auth.com/llms.txt and follow the matching version index.
  5. 5Use the latest documentation only when the project version cannot be determined or the user explicitly asks about the latest release or an upgrade.

When planning an upgrade, separate guidance for the currently installed version from guidance for the target version.


Setup Workflow

  1. 1Install: npm install better-auth
  2. 2Set env vars: BETTER_AUTH_SECRET and BETTER_AUTH_URL
  3. 3Create auth.ts with database + config
  4. 4Create route handler for your framework
  5. 5Run migrations: - Built-in adapter: npx auth@latest migrate - Drizzle: npx auth@latest generate --output src/db/auth-schema.ts then npx drizzle-kit push (dev) or npx drizzle-kit generate && npx drizzle-kit migrate (prod) - Prisma: npx auth@latest generate --output prisma/schema.prisma then npx prisma migrate dev
  6. 6Verify: 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 32
  • BETTER_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 auth@latest migrate - Apply schema (built-in adapter)
  • npx auth@latest generate - Generate schema for Prisma/Drizzle
  • npx auth@latest mcp --cursor - Add MCP to AI tools

Re-run after adding/changing plugins.


Core Config Options

OptionNotes
appNameOptional display name
baseURLOnly if BETTER_AUTH_URL not set
basePathDefault /api/auth. Set / for root.
secretOnly if BETTER_AUTH_SECRET not set
databaseRequired for most features. See adapters docs.
secondaryStorageRedis/KV for sessions & rate limits
emailAndPassword{ enabled: true } to activate
socialProviders{ google: { clientId, clientSecret }, ... }
pluginsArray of plugins
trustedOriginsCSRF whitelist

Database

Direct connections: Pass pg.Pool, mysql2 pool, better-sqlite3, or bun:sqlite instance. For Postgres, also supports postgres (postgres.js) and @neondatabase/serverless.

ORM adapters: Import from better-auth/adapters/drizzle, better-auth/adapters/prisma, better-auth/adapters/mongodb.

Drizzle provider values: "pg" (PostgreSQL), "mysql" (MySQL), "sqlite" (SQLite). Must match the driver used.

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:

  1. 1If secondaryStorage defined → sessions go there (not DB)
  2. 2Set session.storeSessionInDatabase: true to also persist to DB
  3. 3No 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 work
  • emailVerification.sendOnSignUp / sendOnSignIn - Auto-send triggers
  • emailAndPassword.sendResetPassword - Password reset email handler

Security

In advanced:

  • useSecureCookies - Force HTTPS cookies
  • disableCSRFCheck - ⚠️ Security risk
  • disableOriginCheck - ⚠️ Security risk
  • crossSubDomainCookies.enabled - Share cookies across subdomains
  • ipAddress.ipAddressHeaders - Custom IP headers for proxies
  • database.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

  1. 1Model vs table name - Config uses ORM model name, not DB table name
  2. 2Plugin schema - Re-run CLI after adding plugins
  3. 3Secondary storage - Sessions go there by default, not DB
  4. 4Cookie cache - Custom session fields NOT cached, always re-fetched
  5. 5Stateless mode - No DB = session in cookie only, logout on cache expiry
  6. 6Change email flow - Sends to current email first, then new email
  7. 7Drizzle: db not initialized - drizzleAdapter(db, ...) requires a db instance from drizzle(). See create-auth skill for setup examples (node-postgres, postgres.js, Neon).
  8. 8Drizzle: missing drizzle.config.ts - drizzle-kit commands require a drizzle.config.ts pointing to the generated schema file and DB credentials.

Resources

  • Docs
  • Options Reference
  • LLMs.txt
  • GitHub
  • Init Options Source

keep it where your ai can reach it.

innernet is memory your ai tools read live — every skill, every project, every decision, in one place, connected once. save this skill to yours, or publish one of your own as a link like this.

save it to your innernetsee every skill