Over the past week, the team shipped a critical OTP race condition fix that prevented reuse attacks across concurrent requests. Major feature work includes organization-scoped API keys with multi-configuration support (plugin now at @better-auth/api-key), user enumeration protection during signup, and a new /update-session endpoint for custom session fields. Infrastructure improvements address the Drizzle adapter's date handling, add Cloudflare D1 Workers support, and migrate to Prisma v7. The updates also include ID token persistence fixes for OAuth token refresh, improved session broadcasting across browser tabs, and stricter auth_time claim stability per OpenID Connect specs. Bug fixes cover cookie handling, SIWE validation, and better error codes. Extensive work on CI reliability, test timeouts, and documentation including a new landing page.
Highlights
Critical: OTP race condition allows reuse across concurrent requests
Concurrent POST requests could verify the same OTP multiple times before deletion, bypassing single-use constraints and enabling account takeover. Fixed using atomic delete-before-verify across all OTP endpoints (email-otp, sign-in, password reset). #8067 (Author: @Oluwatobi-Mustapha)
API Keys now scoped to organizations with multi-config support
The api-key plugin now lives at @better-auth/api-key and supports multiple configurations per instance. Keys can be owned by organizations instead of just users. Schema changes: userId becomes referenceId, plus new configId and references fields. #4210 (Author: @ping-maxwell)
User enumeration prevention during signup
When email verification or manual signup is enabled, duplicate email attempts now return success without creating a session. Eliminates timing attacks that leak registered accounts. Optional onExistingUserSignUp callback for notifying account owners. #8091 (Author: @bytaesu)
ID token persistence in getAccessToken auto-refresh
getAccessToken was returning a new idToken from token refresh but not persisting it, leaving identity claims stale in storage. Now saved alongside access and refresh tokens. #8211 (Author: @GautamBytes)
Built-in Cloudflare D1 database support
Added Kysely dialect for Cloudflare D1. BetterAuth now runs natively on Workers without custom adapters. Auto-detection via batch/exec/prepare method checks. #7519 (Author: @bytaesu)
More Updates
Security
- Keep auth_time stable across ID token refresh #8134 - Per OpenID Connect Core 1.0, auth_time must not change when tokens refresh. Captures original login time and carries it through token rotation. Requires migration: add nullable authTime column to oauthRefreshToken. (Author: @grant0417)
Features & Enhancements
- POST /update-session endpoint #8084 - Update custom session fields and refresh the session cookie. (Author: @himself65)
- CLI upgrade command #8204 -
npx auth upgradescans package.json for better-auth deps, fetches latest versions, shows a table, and installs pinned versions. (Author: @himself65)
Bug Fixes
- Session changes broadcast to other tabs #8177 - Sign-out and user updates now post to other open tabs so useSession stays in sync. Fixes dead-code broadcast function. (Author: @Abhinav-kodes)
- Drizzle adapter date handling crash #8105 - Removed input transformation that stringified dates before insert. Dates now convert only on read. (Author: @ping-maxwell)
- Drizzle camelCase key mapping #8176 - Schema introspection now handles camelCase column names on PostgreSQL. (Author: @jonathan-teamstatus)
- Prevent unnecessary cookie leak in Next.js #8193 - Remove spurious cookie from response headers. (Author: @ping-maxwell)
- Multi-session setActive validation #8121 - setActive now requires multi-session cookies enabled. (Author: @Oluwatobi-Mustapha)
- Delete expired Expo cookies #8090 - Properly remove expired cookies instead of storing empty values. (Author: @bytaesu)
- Fix bearer token cookie merging #8089 - Use semicolon to merge multiple Set-Cookie headers. (Author: @bytaesu)
- Handle undefined two-factor options #8162 - verifyTwoFactor no longer crashes when options are missing. (Author: @okisdev)
- Remove chainId validation limit #8123 - SIWE no longer rejects newer networks with high chain IDs. (Author: @orshih6)
- Use invalid_grant for refresh token errors #8103 - Return correct OAuth error code. (Author: @luchersou)
- Respect freshAge in deleteUser #8174 - User deletion now checks freshAge requirement. (Author: @bytaesu)
- Export getAccountCookie #8181 - Cookie helper available from better-auth/cookies for stateless flows. (Author: @bytaesu)
- Fix Next.js module resolution #8178 - Use explicit .js extension in next/headers imports. (Author: @Jordanburch101)
- Fix Stripe success URL #8095 - Use checkout session ID for redirect. (Author: @bytaesu)
- Dynamic baseUrl context properties #8170 - All auth context properties now available when baseUrl is dynamic. (Author: @Engerim)
Infrastructure
- Prisma v7 migration #8166 - Updated prisma-adapter and CLI. Changed: datasourceUrl now passed to PrismaClient constructor; call
prisma generateexplicitly after db push. (Author: @himself65) - Expo SDK 55 compatibility #8213 - Updated expo-constants, expo-linking, expo-network, expo-web-browser to SDK 55; widened expo-network peerDependency. (Author: @himself65)
- Per-job CI concurrency groups #8215 - Test matrix jobs can now run in parallel. (Author: @himself65)
- CI timeout and test splitting #8210, #8209, #8208 - Reduced flakiness in SSO, API key, OAuth provider, and Stripe tests.
- Dependency upgrades #8183 - fast-xml-parser and other packages. (Author: @himself65)
- Package.json alignment #8131 - Added missing READMEs and aligned package fields across monorepo. (Author: @himself65)
- Node 24 as default #8129 - Bumped minimum Node version. (Author: @himself65)
- Bump pnpm #8130 - Updated package manager. (Author: @himself65)
Documentation
- New documentation and landing page #8195 - Moved landing site into main repository. (Author: @ping-maxwell)
- Legacy /docs/errors redirect #8160 - Old error documentation links point to new structure. (Author: @bytaesu)
- Convex schema generation #8141 - Clarified confusing setup steps.
- Homepage SVG paths #8200 - Fixed broken image references.
- Community plugins #8138, #8182 - Added better-auth-razorpay, better-auth-payu, and better-auth-audit-logs.
- Admin plugin Electron docs #8171 - Added image proxy callout.
- Captcha ERROR_CODES export #8136 - Error codes now importable. (Author: @marcellosso)
- Description and code alignment #8132 - Fixed documentation inconsistencies. (Author: @bytaesu)
- Electron build config #8126 - Improved tooling. (Author: @jslno)
- Electron OAuth redirect #8143 - Fixed OAuth flow for Electron apps. (Author: @jslno)