new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Better Auth · all subjects

better auth/features

75 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Better Auth built-in advanced features

Better Auth includes advanced features built-in: 2FA, multi-tenancy, multi-session, rate limiting, and many more.

1.7 behavior change: safer OAuth token handling

A refresh token used by a different client, a replayed authorization code, and a `redirect_uri` that does not match the one used at login are now rejected with the correct standard error. A replayed code also revokes the opaque tokens it already issued; an already-minted JWT access token is not stored, so it stays valid until it expires, though it reads as inactive at introspection and userinfo once the session ends. Well-behaved clients are unaffected.

1.7 behavior change: no caching of credentials

Token, introspection, userinfo, registration, and device-authorization responses now send `Cache-Control: no-store` so proxies and browsers do not cache them.

1.7 behavior change: Drizzle affected-row validation

The Drizzle adapter throws on an invalid affected-row count instead of returning `0`.

1.7 behavior change: OAuth authorize error redirect

A missing `response_type` now redirects the error to the verified client `redirect_uri` instead of a generic error.

1.7 behavior change: CLI `generate --output`

CLI `generate --output` to a directory picks an adapter-specific default filename.

1.7 behavior change: userinfo rejects bad tokens

An invalid access token at the userinfo endpoint returns `401 invalid_token` with a `WWW-Authenticate` header.

1.7 behavior change: generated schema and disabled migrations

References to migration-disabled models are omitted from generated schema.

1.7 behavior change: cookie-cache session binding

The cached session is now tied to the `session_token` cookie.

1.7 behavior change: `updateMemberRole` ordering

Role-existence validation now runs after authorization checks in `updateMemberRole`.

Captcha matches full paths in 1.7

Captcha rules now match full request paths or explicit wildcards, which closes a way to skip a captcha rule through partial path matching. Replace a partial path like `/sign-in` with `/sign-in/*` or `/sign-in/**`. The built-in `/sign-in/email-otp` exemption is removed. A `/sign-in/*` wildcard now also matches `/sign-in/email-otp`, so gating it makes email-OTP sign-in return `400 MISSING_RESPONSE` for clients that do not send `x-captcha-response`. To keep email-OTP un-gated, list the exact endpoints you protect or have the email-OTP client send a captcha token.

1.7 behavior change: SSRF host checks

Outbound-host classification now blocks additional reserved ranges: 6to4 relay anycast, site-local IPv6, and IPv4-compatible IPv6.

1.7 behavior change: standard token-redemption errors

Authorization-code redemption failures return `400 invalid_grant` instead of `401 invalid_client` or `invalid_request`.

1.7 behavior change: sign-out hooks with external session stores

`session.delete` hooks now run on sign-out even with `secondaryStorage` and `preserveSessionInDatabase`.

1.7 behavior change: two-factor invalidation error code

A failed two-factor challenge cleanup now returns `FAILED_TO_INVALIDATE_TWO_FACTOR_CHALLENGE`.

1.7 behavior change: `organization.updateTeam` immutable fields

`id`, `createdAt`, and `updatedAt` are no longer accepted in the request body for `organization.updateTeam`.

Better Auth supports stateless session management

Better Auth supports stateless session management without any database, as an alternative to database-backed sessions.

Cookie caching maxAge option

The maxAge option for cookie caching specifies the cache duration in seconds. The example uses 5 * 60 (300 seconds or 5 minutes).

better-auth/minimal bundle size optimization

Use better-auth/minimal instead of better-auth when using custom adapters like Prisma, Drizzle, or MongoDB to reduce bundle size. The minimal version excludes Kysely, which is only needed for direct database connections.

better-auth/minimal limitations

better-auth/minimal does not support direct database connections (an adapter must be used) and does not support built-in migrations (external migration tools or the full better-auth must be used instead).

Cookie caching configuration

Cookie caching stores session data in a short-lived, signed cookie to avoid calling the database every time useSession or getSession is invoked. Enable it with session.cookieCache.enabled set to true and session.cookieCache.maxAge set to the cache duration in seconds.

Background tasks configuration

Background tasks allow deferring non-critical work (cleanup, analytics, rate limit updates, email sending) to run after the response is sent on serverless platforms. Configure this using advanced.backgroundTasks with a handler option. Use ctx.context.runInBackground or ctx.context.runInBackgroundOrAwait in hooks.

Account linking configuration

To ensure unique email addresses like in WorkOS, configure account.accountLinking with enabled set to true and trustedProviders as an array listing providers such as 'email-password' and 'github'.

Extending user schema with additional fields

To extend the user schema with additional fields, use the user.additionalFields option. Define custom fields with type (e.g., 'json'), required boolean, and defaultValue. For example, a metadata field with type 'json', required false, and defaultValue null.

Better Auth Infrastructure key features

Better Auth Infrastructure provides four key features: Dashboard for managing users, organizations, sessions, and viewing analytics from a single interface; Security to protect against credential stuffing, bots, disposable emails, and impossible travel; Email & SMS for sending verification emails, password resets, and OTP codes with pre-built templates; and Enterprise features including SSO/SAML, directory sync (SCIM), log drains, and role-based dashboard access.

verify-email template variables

The verify-email template requires: verificationUrl, userEmail. Optional variables: verificationCode (for code-based verification), userName, appName, expirationMinutes (string).

sign-in-otp template variables

The sign-in-otp template requires: otpCode, userEmail. Optional variables: appName, expirationMinutes (string, default "10").

verify-email-otp template variables

The verify-email-otp template requires: otpCode, userEmail. Optional variables: appName, expirationMinutes (string, default "10").

reset-password-otp template variables

The reset-password-otp template requires: otpCode, userEmail. Optional variables: appName, expirationMinutes (string, default "10").

magic-link template variables

The magic-link template requires: magicLink, userEmail. Optional variables: appName, expirationMinutes (string, default "15").

two-factor template variables

The two-factor template requires: otpCode, userEmail. Optional variables: userName, appName, expirationMinutes (string, default "5").

application-invite template variables

The application-invite template requires: inviteLink, inviterName, inviterEmail, inviteeEmail. Optional variables: appName, expirationDays (string, default "7").

reset-password template variables

The reset-password template requires: resetLink, userEmail. Optional variables: userName, appName, expirationMinutes (string).

sendEmail function basic usage

sendEmail is called with an object containing template (string), to (recipient email), and variables (object with template-specific fields). Example: await sendEmail({ template: "verify-email", to: "user@example.com", variables: { verificationUrl: "...", userEmail: "...", userName: "...", appName: "..." } }).

createEmailSender function for reusable sender

createEmailSender creates a reusable email sender instance. It takes a config object with apiKey and optional apiUrl properties. The returned object has a send() method with the same signature as sendEmail().

change-email template variables

The change-email template requires: confirmationLink, newEmail, currentEmail. Optional variables: userName, appName, expirationMinutes (string).

delete-account template variables

The delete-account template requires: deletionLink, userEmail. Optional variables: userName, appName, expirationMinutes (string, default "60").

stale-account-admin template variables

The stale-account-admin template requires: userEmail, userId (string), adminEmail, daysSinceLastActive (string), loginTime (string). Optional variables: userName, appName, loginLocation, loginDevice, loginIp.

EmailConfig interface

EmailConfig interface has two optional properties: apiKey (string, Better Auth Infrastructure API key) and apiUrl (string, custom API URL).

Email service environment variables

The email service automatically reads BETTER_AUTH_API_KEY and optional BETTER_AUTH_API_URL from environment variables. BETTER_AUTH_API_URL defaults to https://api.betterauth.com if not specified.

SendEmailResult response interface

SendEmailResult interface contains: success (boolean), messageId (optional string, email provider message ID), error (optional string, error message if failed).

Email service plan requirements

Transactional email feature is available on Pro, Business, and Enterprise plans only. Not available on Starter plan.

Email service security pitfall: avoid awaiting in production

Avoid awaiting the email sending in production to prevent timing attacks. On serverless platforms, use waitUntil or similar to ensure the email is sent without blocking the response.

SMS service package import

The SMS service is provided by the @better-auth/infra package. Import sendSMS and createSMSSender functions from @better-auth/infra.

SendSMSOptions parameters

SendSMSOptions has three properties: 'to' (string, required) for phone number in E.164 format, 'code' (string, required) for the OTP code to send, and 'template' (SMSTemplateId, optional) for template selection which defaults to generic if not specified.

SMS templates available

Four SMS templates are available: 'phone-verification' for phone number verification with message 'Your verification code is [code]. It expires in 10 minutes.', 'two-factor' for two-factor authentication with message 'Your two-factor authentication code is [code]. Do not share this code with anyone.', 'sign-in-otp' for passwordless sign-in with message 'Your sign-in code is [code]. It expires in 10 minutes.', and a default generic template with message 'Your verification code is [code].' when no template is specified.

E.164 phone number format requirement

Phone numbers must be in E.164 format with structure +[country code][number]. Valid examples: US +14155551234, UK +447911123456, Germany +4915112345678, Japan +819012345678. Invalid formats include missing + prefix, spaces, dashes, or parentheses.

SMSConfig interface

SMSConfig interface has two optional properties: apiKey (string) for the Better Auth Infrastructure API key, and apiUrl (string) for a custom API URL.

SMS environment variables

The SMS service automatically reads two environment variables: BETTER_AUTH_API_KEY for the API key and BETTER_AUTH_API_URL for optional custom API URL (defaults to https://api.betterauth.com).

sendSMS function signature

The sendSMS function accepts options object with SendSMSOptions and optional SMSConfig, returning a Promise<SendSMSResult>. Signature: async function sendSMS(options: SendSMSOptions, config?: SMSConfig): Promise<SendSMSResult>

createSMSSender function

The createSMSSender function accepts optional SMSConfig and returns a reusable SMS sender instance with a send method that accepts SendSMSOptions.

SendSMSResult response format

SendSMSResult is an interface with boolean success property indicating delivery status, optional messageId string property containing the SMS provider message ID, and optional error string property containing error message if failed.

SMS service error scenarios

Common SMS errors are 'API key not configured' when BETTER_AUTH_API_KEY is missing, and 'Invalid phone number' when the phone number is not in E.164 format. Other delivery errors may occur.

SMS service availability by plan

Transactional SMS is available on Pro, Business, and Enterprise plans. It is not available on the Starter plan.

SMS intended use restriction

SMS delivery is intended only for authentication flows and should not be used for other purposes.

Single SMS send example

Example of sending a single SMS: await sendSMS({ to: "+1234567890", code: "123456", template: "phone-verification", });

SMS sender creation and usage example

Example of creating a reusable SMS sender: const smsSender = createSMSSender({ apiKey: process.env.BETTER_AUTH_API_KEY, apiUrl: process.env.BETTER_AUTH_API_URL, }); await smsSender.send({ to: "+1234567890", code: "123456", template: "two-factor", });

Better Auth phone plugin integration example

Example integrating SMS with Better Auth phone plugin: import { betterAuth } from "better-auth"; import { phoneNumber } from "better-auth/plugins"; import { dash } from "@better-auth/infra"; export const auth = betterAuth({ plugins: [ phoneNumber({ sendOTP: async ({ phoneNumber, code }) => { await sendSMS({ to: phoneNumber, code, template: "phone-verification", }); }, }), dash({ apiKey: process.env.BETTER_AUTH_API_KEY, }), ], });

Stateless session management without database

Better Auth can be configured to work in a stateless mode without a database. If no database is configured, stateless session management is used, but note that most plugins will require a database.

User image proxy in Electron

The Electron plugin securely proxies user avatar images through a custom user-image:// protocol to avoid CSP issues. Use the image URL directly in the renderer, or use user-image://<user-id> format. To access avatars for users other than the current one, enable the Admin plugin.

Give your agent this brain