B

better-auth-best-practices

Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.

SKILL.md


Better 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

  • 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 @better-auth/cli@latest migrate - Apply schema (built-in adapter)
  • npx @better-auth/cli@latest generate - Generate schema for Prisma/Drizzle
  • npx @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:

    1. If secondaryStorage defined → sessions go there (not DB)

    2. Set session.storeSessionInDatabase: true to also persist to DB

    3. 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 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. Model vs table name - Config uses ORM model name, not DB table name

    2. Plugin schema - Re-run CLI after adding plugins

    3. Secondary storage - Sessions go there by default, not DB

    4. Cookie cache - Custom session fields NOT cached, always re-fetched

    5. Stateless mode - No DB = session in cookie only, logout on cache expiry

    6. Change email flow - Sends to current email first, then new email


    ---


    Resources


  • Docs
  • 安装方式

    使用 pa-skills CLI 安装此技能:

    pa-skills add better-auth-best-practices

    需要已安装 pa-skills CLI

    资源下载

    下载此技能的完整资源包:

    下载 ZIP

    包含此技能的所有文件

    相关技能

    查看更多 →