Better Auth Knowledge Patch
Use this skill when implementing, upgrading, debugging, or reviewing Better Auth applications. Start with the quick references below, then load the topic file that matches the task.
Reference index
| Reference |
Topics |
| agent-auth.md |
Agent Auth discovery, JWT profiles, grants, approvals, execution, SDK, CLI, and hardening |
| core-authentication.md |
Email/password, verification, account linking, social providers, usernames, and account deletion |
| data-model-and-adapters.md |
IDs, schema generation, joins, hooks, official adapters, D1, Drizzle, Kysely, SQLite, and PostgreSQL |
| framework-integrations.md |
Convex, Electron, Encore, Expo, Express, Lynx, NestJS, Next.js, SvelteKit, TanStack Start, Waku, and Workers |
| identity-and-utility-plugins.md |
Two-factor, passkeys, OTP, magic links, phone, API keys, anonymous, bearer, JWT, SIWE, captcha, One Tap, and utility plugins |
| infrastructure-and-observability.md |
Managed infrastructure, audit logs, Sentinel, email/SMS, OpenTelemetry, background tasks, and request-state diagnostics |
| migration-guides.md |
Schema, package, endpoint, callback, hook, type, and plugin migrations |
| oauth-oidc-and-mcp.md |
OAuth 2.1 provider, OIDC, generic OAuth, device authorization, resource servers, and MCP migration |
| organizations-sso-and-scim.md |
Organizations, teams, dynamic roles, SSO, SAML, SCIM, provisioning, and domain trust |
| payments.md |
Autumn, Stripe, Polar, Creem, Dodo Payments, and Openfort |
| plugin-development-and-testing.md |
Middleware, plugin context, error codes, adapter predicates, lifecycle hooks, testing, and OpenAPI |
| sessions-cookies-and-security.md |
Stateless auth, session placement, cookie cache, rate limits, CSRF/origin checks, secret rotation, and token storage |
| upgrades-and-cli.md |
Standalone CLI, ESM, package alignment, release tracks, runtime compatibility, and removed exports |
Breaking changes and migrations
Use the standalone CLI
Run the standalone executable instead of the old package-scoped CLI:
npx auth init
npx auth migrate
npx auth generate --adapter prisma
npx auth upgrade
The distribution is ESM-only. Convert CommonJS loading before upgrading.
Update extracted package imports
Several features now have dedicated packages:
import { sso } from "@better-auth/sso";
import { scim } from "@better-auth/scim";
import { passkey } from "@better-auth/passkey";
import { apiKey } from "@better-auth/api-key";
import { oauthProvider } from "@better-auth/oauth-provider";
Redis-backed secondary storage is installed from @better-auth/redis-storage.
Database adapters also have @better-auth/*-adapter packages. Direct adapter imports paired with better-auth/minimal reduce the imported distribution.
Apply schema migrations before serving traffic
Important schema changes include:
- Replace organization member
teamId with the teamMembers join table.
- Rename API-key
userId to referenceId and add configId.
- Rename OIDC
redirectURLs to redirectUrls.
- Migrate legacy OAuth-provider tables, secrets, and token values before switching providers.
- Generate tables for Agent Auth, dynamic organization roles, activity tracking, and persisted billing when those features are enabled.
- Rerun generation or migration after enabling database joins.
Replace removed and renamed APIs
await authClient.requestPasswordReset({ email }); // not forgotPassword
await auth.api.accountInfo({ query }); // GET/query, not POST/body
Use createAdapterFactory instead of createAdapter, afterEmailVerification instead of onEmailVerification, and permissions instead of the organization model's permission field. See the migration reference for removed endpoint, type, export, and callback details.
Treat post-transaction hooks correctly
Database create.after, update.after, and delete.after hooks run after commit. Put atomic follow-up writes in the adapter operation or transaction, not in an after hook.
Sessions and security
Database-free auth is explicit through omission
Omitting database enables stateless session management. Account data can be kept in a signed cookie, and token/account endpoints remain available:
const auth = betterAuth({
socialProviders: { google: { clientId, clientSecret } },
account: { storeAccountCookie: true },
});
When secondary storage is configured, sessions move there by default. Opt into database persistence with storeSessionInDatabase; use preserveSessionInDatabase to retain rows after revocation.
Do not extend freshness by refreshing
Session freshAge is measured from createdAt, not updatedAt. Refreshing cannot keep a session fresh indefinitely. Stateless cookie-cache maxAge is bounded by session expiresIn.
Rotate secrets without invalidating all data
Put the newest key first and retain older keys for decryption:
secrets: [
{ version: 2, value: process.env.AUTH_SECRET_V2! },
{ version: 1, value: process.env.AUTH_SECRET_V1! },
]
Separate CSRF and origin decisions
CSRF and origin validation are independent controls. disableCSRFCheck disables only CSRF defense; disableOriginCheck also disables callback/redirect validation and retains broader legacy disabling behavior. Never trust forwarded host/protocol headers unless a front proxy strips attacker-supplied values.
Encrypt provider tokens when required
Provider access and refresh tokens are plain by default. Prefer the built-in option:
account: { encryptOAuthTokens: true }
OAuth, OIDC, SSO, and SCIM
Build new authorization servers with OAuth Provider
@better-auth/oauth-provider implements OAuth 2.1 and OIDC authorization-code, refresh-token, and client-credentials flows, plus discovery, dynamic registration, consent, introspection, revocation, JWT/JWKS, PKCE policy, and rate limits.
plugins: [
jwt(),
oauthProvider({ loginPage: "/sign-in", consentPage: "/consent" }),
]
Mount both well-known metadata handlers at the issuer. A separate API also needs protected-resource metadata and should validate access tokens with verifyAccessToken, a resource client, or introspection for opaque tokens.
Retire the legacy MCP authorization server
Move authorization-server duties from the legacy built-in MCP plugin to @better-auth/oauth-provider. Separate MCP resource servers can verify tokens with createMcpAuthClient.
Harden SSO and provisioning
- Allowlist the OIDC discovery URL and every resolved endpoint.
- Keep SAML assertion replay protection and response-correlation validation enabled.
- Export all five SCIM methods:
GET, POST, PUT, PATCH, and DELETE.
- Authorize SCIM token issuance and choose hashed, encrypted, or custom token storage.
Hooks and background work
Use unified middleware
hooks: {
before: createAuthMiddleware(async (ctx) => inspect(ctx.path)),
after: createAuthMiddleware(async (ctx) => inspect(ctx.context.returned)),
}
Plugin hooks remain matcher/handler entries. A plugin init() receives the live mutable auth context and may return context keys for other plugins.
Choose fire-and-forget versus required completion
runInBackground() schedules work after the response. runInBackgroundOrAwait() uses the configured background handler, but awaits when none exists. Connect serverless lifetime primitives through advanced.backgroundTasks.handler; expect eventual consistency when database writes are deferred.
Framework integration checks
- Mount Express auth routes before
express.json(); Express 5 needs a named wildcard.
- Put
nextCookies() last when Server Actions call auth.api directly, and validate full sessions for protected Next.js routes.
- Keep Electron auth state and cookies in the main process and align protocol, callback path, client ID, and trusted origin across server, web, and desktop clients.
- Pair Expo server/client plugins with secure storage; send
getCookie() manually for non-client requests.
- Enable Workers AsyncLocalStorage compatibility before constructing auth.
- Follow the custom
Set-Cookie bridge for Waku Server Actions.
Agent Auth essentials
Agent Auth separates persistent hosts from runtime agents. Each agent has an Ed25519 key, an immutable delegated/autonomous mode, scoped grants, and short-lived single-use Agent JWTs. Discovery starts at /.well-known/agent-configuration; unsupported major protocol versions must stop the client.
Use @better-auth/agent-auth for registration, approval, grant enforcement, and execution. Run its migration, keep automatic default grants low-risk, use durable secondary storage for replay/JWKS caches in multi-instance deployments, and require fresh sessions or WebAuthn proof of presence for sensitive approvals.
Adapter and plugin reminders
advanced.database.generateId may delegate selected models to the database by returning false or undefined.
- Adapter predicates support per-clause
mode: "insensitive".
- Custom session fields are recomputed on every fetch and bypass cookie/secondary caches.
- Passkey registration can run without a session only when the supplied context is securely validated.
- API-key validation consumes limits and budgets; avoid verifying twice before
getSession.
- Defining custom admin/user roles replaces built-in grants unless their statements are merged.
- Payment plugins differ sharply in owner/reference semantics, persistence, cancellation, and entitlement lookup; load the payment reference before implementation.
1---2name: better-auth-knowledge-patch3description: Better Auth4license: MIT5---678# Better Auth Knowledge Patch910Use this skill when implementing, upgrading, debugging, or reviewing Better Auth applications. Start with the quick references below, then load the topic file that matches the task.1112## Reference index1314| Reference | Topics |15| --- | --- |16| [agent-auth.md](references/agent-auth.md) | Agent Auth discovery, JWT profiles, grants, approvals, execution, SDK, CLI, and hardening |17| [core-authentication.md](references/core-authentication.md) | Email/password, verification, account linking, social providers, usernames, and account deletion |18| [data-model-and-adapters.md](references/data-model-and-adapters.md) | IDs, schema generation, joins, hooks, official adapters, D1, Drizzle, Kysely, SQLite, and PostgreSQL |19| [framework-integrations.md](references/framework-integrations.md) | Convex, Electron, Encore, Expo, Express, Lynx, NestJS, Next.js, SvelteKit, TanStack Start, Waku, and Workers |20| [identity-and-utility-plugins.md](references/identity-and-utility-plugins.md) | Two-factor, passkeys, OTP, magic links, phone, API keys, anonymous, bearer, JWT, SIWE, captcha, One Tap, and utility plugins |21| [infrastructure-and-observability.md](references/infrastructure-and-observability.md) | Managed infrastructure, audit logs, Sentinel, email/SMS, OpenTelemetry, background tasks, and request-state diagnostics |22| [migration-guides.md](references/migration-guides.md) | Schema, package, endpoint, callback, hook, type, and plugin migrations |23| [oauth-oidc-and-mcp.md](references/oauth-oidc-and-mcp.md) | OAuth 2.1 provider, OIDC, generic OAuth, device authorization, resource servers, and MCP migration |24| [organizations-sso-and-scim.md](references/organizations-sso-and-scim.md) | Organizations, teams, dynamic roles, SSO, SAML, SCIM, provisioning, and domain trust |25| [payments.md](references/payments.md) | Autumn, Stripe, Polar, Creem, Dodo Payments, and Openfort |26| [plugin-development-and-testing.md](references/plugin-development-and-testing.md) | Middleware, plugin context, error codes, adapter predicates, lifecycle hooks, testing, and OpenAPI |27| [sessions-cookies-and-security.md](references/sessions-cookies-and-security.md) | Stateless auth, session placement, cookie cache, rate limits, CSRF/origin checks, secret rotation, and token storage |28| [upgrades-and-cli.md](references/upgrades-and-cli.md) | Standalone CLI, ESM, package alignment, release tracks, runtime compatibility, and removed exports |2930## Breaking changes and migrations3132### Use the standalone CLI3334Run the standalone executable instead of the old package-scoped CLI:3536```sh37npx auth init38npx auth migrate39npx auth generate --adapter prisma40npx auth upgrade41```4243The distribution is ESM-only. Convert CommonJS loading before upgrading.4445### Update extracted package imports4647Several features now have dedicated packages:4849```ts50import { sso } from "@better-auth/sso";51import { scim } from "@better-auth/scim";52import { passkey } from "@better-auth/passkey";53import { apiKey } from "@better-auth/api-key";54import { oauthProvider } from "@better-auth/oauth-provider";55```5657Redis-backed secondary storage is installed from `@better-auth/redis-storage`.5859Database adapters also have `@better-auth/*-adapter` packages. Direct adapter imports paired with `better-auth/minimal` reduce the imported distribution.6061### Apply schema migrations before serving traffic6263Important schema changes include:6465- Replace organization member `teamId` with the `teamMembers` join table.66- Rename API-key `userId` to `referenceId` and add `configId`.67- Rename OIDC `redirectURLs` to `redirectUrls`.68- Migrate legacy OAuth-provider tables, secrets, and token values before switching providers.69- Generate tables for Agent Auth, dynamic organization roles, activity tracking, and persisted billing when those features are enabled.70- Rerun generation or migration after enabling database joins.7172### Replace removed and renamed APIs7374```ts75await authClient.requestPasswordReset({ email }); // not forgotPassword76await auth.api.accountInfo({ query }); // GET/query, not POST/body77```7879Use `createAdapterFactory` instead of `createAdapter`, `afterEmailVerification` instead of `onEmailVerification`, and `permissions` instead of the organization model's `permission` field. See the migration reference for removed endpoint, type, export, and callback details.8081### Treat post-transaction hooks correctly8283Database `create.after`, `update.after`, and `delete.after` hooks run after commit. Put atomic follow-up writes in the adapter operation or transaction, not in an `after` hook.8485## Sessions and security8687### Database-free auth is explicit through omission8889Omitting `database` enables stateless session management. Account data can be kept in a signed cookie, and token/account endpoints remain available:9091```ts92const auth = betterAuth({93 socialProviders: { google: { clientId, clientSecret } },94 account: { storeAccountCookie: true },95});96```9798When secondary storage is configured, sessions move there by default. Opt into database persistence with `storeSessionInDatabase`; use `preserveSessionInDatabase` to retain rows after revocation.99100### Do not extend freshness by refreshing101102Session `freshAge` is measured from `createdAt`, not `updatedAt`. Refreshing cannot keep a session fresh indefinitely. Stateless cookie-cache `maxAge` is bounded by session `expiresIn`.103104### Rotate secrets without invalidating all data105106Put the newest key first and retain older keys for decryption:107108```ts109secrets: [110 { version: 2, value: process.env.AUTH_SECRET_V2! },111 { version: 1, value: process.env.AUTH_SECRET_V1! },112]113```114115### Separate CSRF and origin decisions116117CSRF and origin validation are independent controls. `disableCSRFCheck` disables only CSRF defense; `disableOriginCheck` also disables callback/redirect validation and retains broader legacy disabling behavior. Never trust forwarded host/protocol headers unless a front proxy strips attacker-supplied values.118119### Encrypt provider tokens when required120121Provider access and refresh tokens are plain by default. Prefer the built-in option:122123```ts124account: { encryptOAuthTokens: true }125```126127## OAuth, OIDC, SSO, and SCIM128129### Build new authorization servers with OAuth Provider130131`@better-auth/oauth-provider` implements OAuth 2.1 and OIDC authorization-code, refresh-token, and client-credentials flows, plus discovery, dynamic registration, consent, introspection, revocation, JWT/JWKS, PKCE policy, and rate limits.132133```ts134plugins: [135 jwt(),136 oauthProvider({ loginPage: "/sign-in", consentPage: "/consent" }),137]138```139140Mount both well-known metadata handlers at the issuer. A separate API also needs protected-resource metadata and should validate access tokens with `verifyAccessToken`, a resource client, or introspection for opaque tokens.141142### Retire the legacy MCP authorization server143144Move authorization-server duties from the legacy built-in MCP plugin to `@better-auth/oauth-provider`. Separate MCP resource servers can verify tokens with `createMcpAuthClient`.145146### Harden SSO and provisioning147148- Allowlist the OIDC discovery URL and every resolved endpoint.149- Keep SAML assertion replay protection and response-correlation validation enabled.150- Export all five SCIM methods: `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`.151- Authorize SCIM token issuance and choose hashed, encrypted, or custom token storage.152153## Hooks and background work154155### Use unified middleware156157```ts158hooks: {159 before: createAuthMiddleware(async (ctx) => inspect(ctx.path)),160 after: createAuthMiddleware(async (ctx) => inspect(ctx.context.returned)),161}162```163164Plugin hooks remain matcher/handler entries. A plugin `init()` receives the live mutable auth context and may return context keys for other plugins.165166### Choose fire-and-forget versus required completion167168`runInBackground()` schedules work after the response. `runInBackgroundOrAwait()` uses the configured background handler, but awaits when none exists. Connect serverless lifetime primitives through `advanced.backgroundTasks.handler`; expect eventual consistency when database writes are deferred.169170## Framework integration checks171172- Mount Express auth routes before `express.json()`; Express 5 needs a named wildcard.173- Put `nextCookies()` last when Server Actions call `auth.api` directly, and validate full sessions for protected Next.js routes.174- Keep Electron auth state and cookies in the main process and align protocol, callback path, client ID, and trusted origin across server, web, and desktop clients.175- Pair Expo server/client plugins with secure storage; send `getCookie()` manually for non-client requests.176- Enable Workers AsyncLocalStorage compatibility before constructing auth.177- Follow the custom `Set-Cookie` bridge for Waku Server Actions.178179## Agent Auth essentials180181Agent Auth separates persistent hosts from runtime agents. Each agent has an Ed25519 key, an immutable delegated/autonomous mode, scoped grants, and short-lived single-use Agent JWTs. Discovery starts at `/.well-known/agent-configuration`; unsupported major protocol versions must stop the client.182183Use `@better-auth/agent-auth` for registration, approval, grant enforcement, and execution. Run its migration, keep automatic default grants low-risk, use durable secondary storage for replay/JWKS caches in multi-instance deployments, and require fresh sessions or WebAuthn proof of presence for sensitive approvals.184185## Adapter and plugin reminders186187- `advanced.database.generateId` may delegate selected models to the database by returning `false` or `undefined`.188- Adapter predicates support per-clause `mode: "insensitive"`.189- Custom session fields are recomputed on every fetch and bypass cookie/secondary caches.190- Passkey registration can run without a session only when the supplied context is securely validated.191- API-key validation consumes limits and budgets; avoid verifying twice before `getSession`.192- Defining custom admin/user roles replaces built-in grants unless their statements are merged.193- Payment plugins differ sharply in owner/reference semantics, persistence, cancellation, and entitlement lookup; load the payment reference before implementation.