better-auth
Features
- Added an experimental
oauthPopupplugin for popup-based OAuth sign-in, enabling sign-in inside cross-site iframes by completing the OAuth flow in a popup and passing the session token back via thebearerplugin (#9890)
Bug Fixes
- Fixed
getCookieCacheto returnnullfor an expired session instead of stale data, so middleware no longer treats an expired signed cookie as a live session. - Fixed a race condition where a delete-account confirmation link could delete the account more than once when its callback was opened concurrently.
- Fixed a race condition where a one-time token could be redeemed for a session more than once when redeemed concurrently.
- Fixed a race condition where a password reset token could change the password more than once when used from concurrent requests.
- Fixed Reddit sign-in to assign a non-routable placeholder address (
<id>@reddit.invalid) to users with no email, instead of one on the realreddit.comdomain, preventing accidental mailbox matches. The address stays unverified, andmapProfileToUsercan supply a real email. - Fixed Sign-In with Ethereum to prevent a nonce from being used to sign in more than once when submitted from concurrent requests.
- Added
internalAdapter.reserveVerificationValuefor atomic single-use markers, ensuring exactly one concurrent caller succeeds and the rest see the marker as already taken, hardening replay protection across all verification flows. Database-backed storage is atomic; secondary-storage-only mode is best-effort. - Added the optional
incrementOneadapter method andSecondaryStorage.incrementfor atomic counter updates with conditional row guards, enabling strict enforcement of rate limits and usage counters. Adapters without native support fall back to a transaction-based approach. - Fixed expired two-factor sign-in challenges from completing login, and prevented the same challenge from creating more than one session when verified concurrently.
- Fixed captcha provider verification to time out after 10 seconds and fail closed, preventing a slow or unreachable provider from blocking requests indefinitely.
- Fixed
/delete-user/callbackto reject account deletion when the session has been revoked server-side, instead of proceeding within the cookie-cache window. Deployments that keep sessions only in the cookie are unaffected. - Fixed concurrent requests from slipping past the configured rate limit, resolved unbounded memory growth in the in-memory rate-limit store, and made the database backend remove expired entries automatically. A custom rate-limit storage may implement a new optional
consumemethod for strict enforcement. - Fixed team deletion to preserve pending invitations, which now drop the removed team and remain valid for their remaining teams or as organization-level invitations.
- Downgraded expected auth validation failures from error logs to warnings.
- Fixed expired MCP access tokens from being accepted, and restricted refresh token acceptance to authorizations that included the
offline_accessscope. - Fixed team member limits to be enforced on
addMemberandadd-team-memberpaths, preventing teams from exceeding theirmaximumMembersPerTeamcap, and ensured a rejectedaddMemberdoes not create the organization member (#10002) - Fixed generic OAuth sign-in for providers whose userinfo response lacks a
suboridfield whenmapProfileToUserderives the account id (#9987) - Fixed single-use credentials, counters, and replay markers to be handled atomically under concurrent requests (#9993)
- Fixed stateless OAuth deployments to correctly read account info and tokens when different server instances handle sign-in and subsequent requests (#9979)
- Fixed
admin.setUserPasswordto create a credential account for users who only have social or magic-link accounts, enabling direct password assignment without manually modifying theaccounttable (#9482) - Fixed
updateSessionto accept custom session fields inferred frominferAdditionalFields(#9777) - Fixed duplicate
/get-sessionrequests triggered by focus and other browser events, stabilized client hook data references to reduce unnecessary re-renders, and resolved session state getting stuck loading after unmounting during an in-flight request (#8760) - Fixed the OpenAPI schema to mark model
idfields as required (#9704) - Fixed
updateMemberRoleto reject unknown or malformed role values, validating them against configured static and dynamic roles (#9962) - Fixed a memory leak where the JWKS cache grew on every access token verification.
- Fixed Google One Tap to require a configured client ID and reject ID tokens issued for a different application, preventing unauthorized sign-ins.
- Fixed a race condition where polling for a device-authorization token could redeem the same approved device code more than once.
- Fixed account cookie handling when switching users in the same browser, preserving the fresh cookie instead of expiring it from stale request state.
- Refactored
role.authorizecontrol flow without changing existing authorization behavior (#9677) - Fixed a race condition where submitting the same email OTP from concurrent requests could sign in more than once or exceed the attempt limit.
- Fixed a race condition where submitting the same phone-number OTP from concurrent requests could sign in more than once or exceed the attempt limit.
- Fixed a race condition where submitting the same two-factor OTP from concurrent requests could sign in more than once or exceed the attempt limit.
- Improved the Have I Been Pwned plugin to check submitted passwords on more endpoints by default, including email-OTP and phone-number reset-password routes and admin create-user and set-user-password routes.
- Fixed multi-session
set-activeandrevokeendpoints to only act on sessions the caller holds a signed cookie for, preventing unauthorized activation or revocation of other sessions. - Fixed the OIDC
/oauth2/endsessionendpoint to reject cross-site GET logout requests carrying only a session cookie, while leaving logout authenticated by a validid_token_hintunaffected. - Fixed WeChat sign-in to succeed without an email address by assigning a stable placeholder, matching the behavior expected from the default configuration.
For detailed changes, see CHANGELOG
@better-auth/api-key
Bug Fixes
- Fixed API key updates to fail when the caller's session has been revoked server-side, instead of succeeding within the cookie-cache window (#9991)
- Prevented server-only endpoints from being accidentally exposed over HTTP (#9835)
- Fixed concurrent API key verification from driving the remaining-uses count below zero or exceeding the rate limit. Secondary-storage-only deployments remain best-effort for these counters.
For detailed changes, see CHANGELOG
@better-auth/sso
Bug Fixes
- Fixed SAML replay protection to hold under concurrent requests, preventing a SAML assertion submitted twice simultaneously from being accepted more than once.
- Fixed SSO domain verification to allow organization admins and owners to verify domains for providers their organization owns, not just the member who originally registered the provider.
- Fixed
trustEmailVerifiedto no longer treat the string"false"as a verified email, accepting only a booleantrueor the string"true"as confirmation.
For detailed changes, see CHANGELOG
auth
Bug Fixes
- Fixed the CLI to resolve SvelteKit (
$app/*,$env/*), Vite asset imports (?raw,?url), and Cloudflare Workers (cloudflare:workers) virtual-module imports when loading the auth config (#9834) - Fixed the CLI to skip
Unsupported()fields when regenerating the Prisma schema (#10011) - Fixed the CLI to update existing Prisma field types when regenerating the schema, such as correctly emitting
BigIntorIntwhenbigintconfiguration changes (#9729)
For detailed changes, see CHANGELOG
@better-auth/expo
Bug Fixes
- Hardened request trust validation for the Expo authorization proxy, including rejecting redirect and callback targets not in
trustedOrigins(#9990) - Fixed Expo social account linking to include the stored session cookie when using an ID token with
linkSocial(#9953)
For detailed changes, see CHANGELOG
@better-auth/memory-adapter
Bug Fixes
- Fixed the memory adapter to not discard writes from concurrent operations on a failed transaction, made
updateanddeletewith an empty filter a no-op instead of affecting all rows, and madeupdateManyreturn the number of affected rows. - Fixed counter updates on the memory, Kysely, Drizzle, Prisma, and MongoDB adapters to be atomic by default, ensuring correct rate limiting and API-key usage limit enforcement.
For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
- Fixed organization-scoped SCIM deletes to remove members through the organization adapter, ensuring team memberships and member-removal hooks are applied correctly.
- Fixed SCIM bearer token comparison to use constant-time evaluation, closing a timing side channel that could help an attacker recover a valid token.
For detailed changes, see CHANGELOG
@better-auth/core
Bug Fixes
- Fixed provider identity validation for Google One Tap, Microsoft Entra ID, SSO, WeChat, and Reddit sign-in to enforce tenant restrictions and reject tokens issued for other applications (#10003)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
- Fixed
updateManyto return the number of rows it affected, as the adapter contract specifies.
For detailed changes, see CHANGELOG
@better-auth/electron
Bug Fixes
- Fixed a race condition where an Electron authorization code could be exchanged for a session more than once when the exchange was attempted concurrently.
For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
- Fixed SQLite mutations through the Bun and Node Kysely drivers to correctly report affected row counts and inserted row IDs, fixed multiple query parameter binding in the Bun driver, and made
consumeOnework on SQL Server.
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
Bug Fixes
- Fixed token introspection and revocation to cache signing keys per auth instance instead of fetching them from the database on every request.
For detailed changes, see CHANGELOG
@better-auth/passkey
Bug Fixes
- Fixed passkey challenge validation to reject registration challenges used for authentication (and vice versa), and to fail when the target user cannot be resolved.
For detailed changes, see CHANGELOG
@better-auth/prisma-adapter
Bug Fixes
- Fixed the Prisma adapter to surface
deleteerrors instead of silently reporting success when a deletion fails for any reason other than the record being absent.
For detailed changes, see CHANGELOG
@better-auth/redis-storage
Bug Fixes
- Fixed Redis-backed rate-limit windows to set expiry once when the window opens instead of extending it with continued traffic, and added an atomic
incrementmethod to Redis secondary storage.
For detailed changes, see CHANGELOG
@better-auth/stripe
Bug Fixes
- Fixed several Stripe subscription issues: reused existing customers by email only when verified, synced subscription status from the checkout session on success, scoped cancellation and restoration to the targeted subscription, validated
returnUrlagainsttrustedOrigins, and checked all subscriptions on organization deletion (#9971)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@arnnvv, @Bekacru, @bytaesu, @GautamBytes, @gustavovalverde, @SferaDev
Full changelog: v1.6.16...v1.6.17