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 · Authentication · all subjects

account & data

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

Better Auth is a framework-agnostic authentication framework for TypeScript

Better Auth is a framework-agnostic authentication and authorization framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem for adding advanced functionalities.

Better Auth features include 2FA and multi-tenant support

Better Auth includes built-in support for features such as two-factor authentication (2FA) and multi-tenant support.

Better Auth installation command

To install Better Auth, run the command: npm i better-auth

Device authorization optional user_id pre-binding

The device authorization plugin now accepts an optional user_id when issuing a device code via /device/code, pre-binding the code to that user. Only the bound user can approve or deny the code, so a publicly visible user code can no longer be claimed by someone else.

Session deletion removes sessions from secondary storage

Deleting a user now also removes their sessions from secondary storage.

JWKS key minting uses transaction-scoped adapter

Minting or reading a JWKS signing key inside an active database transaction now uses the transaction-scoped adapter instead of the root connection. On a single-connection SQLite database with native transactions enabled, this no longer deadlocks, and on Postgres and MySQL the key commits with the surrounding transaction instead of independently.

Refresh token endpoint requires account cookie match

/refresh-token now requires the account cookie's userId, providerId, and (when supplied) accountId to match the resolved session user.

Organization invitations can use database-generated IDs

Organization invitations now let the database generate their id when ID generation is delegated to the database (for example with advanced.database.generateId: "uuid" with a UUID-capable adapter such as Postgres). Previously createInvitation always generated the invitation id in application code. A caller-provided id (for example via beforeCreateInvitation) is still honored.

Admin user creation and update permission guards

The admin plugin now guards protected user fields behind dedicated permissions. /admin/create-user requires user:set-role when a role is supplied and validates requested roles, requires user:ban for ban fields, and no longer lets data override email, name, or role. /admin/update-user requires user:ban for banned/banReason/banExpires and rejects password updates in favor of /admin/set-user-password.

Organization invitation team IDs scoped to organization

Organization invitation team IDs are now scoped to the invited organization. createInvitation validates that every requested teamId belongs to the invitation's organization, and acceptInvitation re-checks each stored team's organization before adding team membership. Previously a team ID from another organization could be stored and applied.

Rate limiting trusts single-value IP headers only

Rate limiting no longer trusts multi-hop X-Forwarded-For chains, preventing a client behind an appending proxy from spoofing the leftmost hop to bypass the per-IP rate limit. Single-value IP headers continue to work. To key the real client behind a proxy chain, set advanced.ipAddress.trustedProxies to reverse-proxy IPs or CIDR ranges (the chain is walked right to left, skipping trusted hops), or point advanced.ipAddress.ipAddressHeaders at a single trusted client-IP header.

Rate limit enforcement before plugin handlers

Rate limiting is now enforced on client requests before plugin request handlers run.

Plugin schema table disableMigration flag honored

Tables flagged with disableMigration: true are now skipped by better-auth generate (Drizzle and Prisma output) and by the runtime migrator, instead of being emitted and created anyway.

Malformed redirect parameters return 400

Malformed redirect parameters now return a 400 instead of a 500.

Session deletion invalidates cached cookie immediately

Deleting a session now immediately stops /update-session and the account token endpoints (/get-access-token, /refresh-token, /account-info) from accepting it when cookie cache is enabled alongside a database or secondary storage. Before, these routes kept serving the deleted session from the cached cookie until the cache expired.

Update-session rejects plugin-managed fields

Passing activeOrganizationId, activeTeamId, or impersonatedBy to /update-session now returns a 400. Change these plugin-managed session fields through their dedicated endpoints instead, such as organization.setActive.

Admin plugin user lookup returns NOT_FOUND

The admin plugin's unbanUser, setRole and adminUpdateUser endpoints now look the user up via findUserById and throw a clean NOT_FOUND (USER_NOT_FOUND) when no row is returned, instead of bubbling up database errors.

List-session endpoint requires fresh session

The list-session endpoint now requires a fresh-age session check.

Multi-session set-active and revoke require matching cookie

The multi-session set-active and revoke endpoints now act only on the session the caller holds a signed cookie for. A request could previously activate or revoke a different session by naming its token in the request body without holding that session's cookie.

Session cookie cache cookie chunking

Session and account cache cookies near the browser's per-cookie size limit are now split into chunks instead of being silently dropped by the browser. A cache too large to fit even when chunked is skipped with a warning, so reads fall back to the database.

Captcha provider verification timeout

Captcha provider verification requests now time out after 10 seconds and fail closed, so a slow or unreachable captcha provider can no longer tie up a request indefinitely.

Delete-account confirmation link prevents double deletion

A delete-account confirmation link can no longer delete the account more than once when its callback is opened concurrently.

Delete-account callback fails on revoked session

Completing account deletion through /delete-user/callback now fails when the session has been revoked server-side instead of proceeding within the cookie-cache window.

Device authorization code prevents concurrent redemption

Polling for a device-authorization token can no longer redeem the same approved device code more than once when several polls arrive together.

Team member limit enforced on all paths

Adding a member to a team that is already at its maximumMembersPerTeam limit is now rejected on every path. A rejected addMember no longer creates the organization member.

Organization invitation acceptance handles missing teams gracefully

Deleting a team no longer breaks its pending invitations. The removed team is dropped from those invitations, which stay valid for their remaining teams or as plain organization-level invitations. Accepting an invitation that still references a missing team fails without consuming the invitation.

Account cookie preserved when switching users

The fresh account cookie issued while switching users in the same browser is now preserved instead of being expired from stale request cookie state.

Session cache returns null for expired sessions

getCookieCache now returns null for an expired session instead of the stale session data. Middleware that calls it to gate access no longer treats an expired signed cookie as a live session.

One-time token prevents concurrent redemption

A one-time token can no longer be redeemed for a session more than once when redeemed concurrently.

Phone-number OTP prevents concurrent redemption

Submitting the same phone-number OTP from several requests at once can no longer sign in more than once or gain extra tries beyond the attempt limit.

Rate limit concurrent request enforcement

Concurrent requests can no longer slip past the configured rate limit. The in-memory rate-limit store no longer grows without bound, and the database backend removes expired entries on its own. A custom rate-limit storage may implement a new optional consume method for strict enforcement.

Organization member role validation on update

Roles are now validated when updating an organization member. Roles are normalized into individual tokens and checked against the configured static and dynamic roles, so unknown or malformed role values are rejected instead of being persisted.

MCP access token expiration validation

Expired MCP access tokens are no longer accepted. A protected MCP resource now rejects a bearer token once it has expired, both on the server and through the remote client. A refresh token is accepted only when the original authorization included the offline_access scope.

MCP remote client CORS 401 challenge exposure

The remote MCP auth client's 401 challenge headers are now exposed to browser clients using CORS.

OpenAPI includes additional fields on email endpoints

OpenAPI now includes user.additionalFields and plugin user schema fields (for example username plugin username / displayUsername) on /sign-up/email and /update-user request bodies.

Organization deletion hook context parameter

The endpoint context is now passed as the second argument to beforeDeleteOrganization and afterDeleteOrganization hooks in the organization plugin, matching the signature shown in the docs and the existing databaseHooks pattern. The Stripe plugin's beforeDeleteOrganization wrapper now forwards the context to user-supplied hooks.

jwtClient preserves type inference with other plugins

jwtClient() no longer collapses createAuthClient type inference when combined with other client plugins such as inferAdditionalFields. Additional user fields (for example on updateUser) are preserved again.

Secondary storage handles invalid session entries

Invalid secondary-storage session entries are now skipped without discarding other valid sessions.

Database rate-limit cleanup completes without handler

Database rate-limit cleanup now completes when no background task handler is configured.

Model name alias matching improved in adapter queries

Exact schema-key matches are now preferred over modelName aliases in getDefaultModelName, so remapping a built-in table onto another table's schema key (for example user.modelName = "account") does not reroute internal adapter queries to the wrong table.

Drizzle Kysely migration field uniqueness

Drizzle and Kysely migration generation now handles fields that are both unique: true and index: true correctly.

Organization member listing limit consistency

organization.listMembers now applies the same membership limit to the users query, fixing failures with more than approximately 100 members.

Foreign key collision detection in schema remapping

Silent foreign-key and adapter-join misrouting is now fixed when a user remaps a built-in model name to a string that collides with another schema key.

Verification callback request cloning error handling

Verification callbacks no longer fail auth requests when cloning the request throws.

SQLite BIGINT type migration diff recognition

SQLite BIGINT is now recognized as a valid number type in migration diffs, so database-backed rate limiter columns like lastRequest no longer report spurious pending changes.

Auth query revalidation signal listeners restored after remount

Auth query revalidation and signal listeners are now restored after client remount.

Last-login-method beforeStoreCookie GDPR compliance option

The last-login-method plugin now includes a beforeStoreCookie option for GDPR compliance, allowing custom handling of login method storage.

Session refresh no longer emits Max-Age above browser ceiling

Session refresh no longer emits a cookie Max-Age above the browser's 400-day ceiling when using a database without fractional-second precision.

Refresh token rotation now works on Prisma

Refresh-token rotation and token revocation, two-factor backup-code regeneration, device-code claiming, and organization invitation acceptance now work on Prisma. Concurrent or repeat requests in these flows could previously return an error.

MongoDB guarded value updates compatibility

On MongoDB servers older than 5.0, guarded value updates (rate-limit window resets, API-key refills) no longer fail with an empty-update error.

Last-login-method cross-subdomain cookie clearing

The last-login-method plugin now includes domain when clearing cross-subdomain cookies.

Admin permission changes take effect immediately

Admin permission changes and bans now take effect immediately for admin APIs, even when session cookie cache is enabled. Sensitive session checks also continue to work in stateless apps where signed cookies are the session record.

Device authorization ZodError fix without schema

deviceAuthorization() no longer throws a ZodError at construction when called without a schema option.

Solid client $fetch and $store atoms exposed

The Solid client now exposes the real $fetch instance and $store atoms instead of resolving them as dynamic API routes.

API error property inheritance in TypeScript

Inherited APIError properties are now declared to fix TypeScript inference errors.

Team member limit concurrent acceptance fix

When a team had a single open slot, accepting an invitation into it was wrongly rejected as over the member limit. Two invitations accepted into a nearly-full team at the same time could also push it past its limit. Both are fixed.

BaseURL fallback for password-reset and verification links

When baseURL is not configured, password-reset and verification links now use the current request's host rather than the host of the first request the server handled.

Captcha provider optional hostname and action validation

Google reCAPTCHA and Cloudflare Turnstile now accept optional expectedAction and allowedHostnames to reject tokens minted for a different action or hostname.

IPv6 reserved ranges in server-side fetches

Server-side fetches now reject additional reserved IPv6 ranges.

Root-mounted deployment path validation

In root-mounted deployments, requests whose path does not start with the configured basePath now return 404 instead of resolving to an endpoint.

Give your agent this brain