Update dependency better-auth to v1.7.25 #126
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MobiusReactor/TicTacToeV2!126
Loading…
Reference in a new issue
No description provided.
Delete branch "renovate/better-auth-monorepo"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
1.6.23→1.7.25Release Notes
better-auth/better-auth (better-auth)
v1.7.25Compare Source
Patch Changes
#12603
ad07eddThanks @gustavovalverde! - An error thrown from an endpoint no longer drops theAccess-Control-Expose-Headersnames that a hook or the endpoint set earlier. The names from both are kept. Before, the error's value replaced the list, so a browser client on another origin could lose access to headers a hook had exposed.Updated dependencies [
69e14d8,2a8750b,75c0821]:v1.7.24Compare Source
Patch Changes
#12593
b77e783Thanks @gustavovalverde! - Looking up a verification value now deletes expired verification rows at most once a minute per server instance, instead of on every lookup. This removes a delete query from most verification reads. Each flow still checks a value's expiry before it accepts the value.Updated dependencies [
0597f4c]:v1.7.23Compare Source
Patch Changes
#12543
d6e9bbdThanks @gustavovalverde! -getTestInstancefrombetter-auth/testnow gives each test file its own MongoDB database and closes its MongoDB client on cleanup. Before, every MongoDB test instance shared thebetter-authdatabase and dropped it during cleanup, so test files running in parallel deleted each other's data.Updated dependencies [
9dc64fb,cfa73e9,41a2526,c10b35d]:v1.7.22Compare Source
Patch Changes
#12324
80c407bThanks @bytaesu! - FixedReferenceError: window is not definedwhen a client session subscription is cleaned up after a test environment such as happy-dom tears down its globals.#12462
d32a30aThanks @bytaesu! - Fixed session polling never firing when the runtime'snavigatordoes not report connectivity.#12324
80c407bThanks @bytaesu! - Fixed client session refresh listeners piling up on the server during SSR.#12321
946177eThanks @bytaesu! - Revoking your current session withrevokeSessionorrevokeSessions, and deleting your account withdeleteUser({ token }), now expire the session cookies, so a cached session is no longer returned afterwards.#12459
d210421Thanks @bytaesu! -getSessionno longer leaves an expired session in the request context after returningnull.#12331
485567eThanks @bytaesu! - OAuth sign-up rejected by auser.create.beforehook returningfalseno longer logs aTypeError. It still fails withunable_to_create_user.#12334
ec3047dThanks @bytaesu! - FixeddeleteOrganizationclearing the session's active organization when abeforeDeleteOrganizationhook rejected the delete. The active organization is now cleared only after the organization is actually deleted.#12330
d521343Thanks @bytaesu! - FixdeferSessionRefreshrefresh requests failing with 415 in Next.js and Nuxt, where the bodylessPOST /get-sessionreached the handler as an empty stream without aContent-Type.#12451
71052afThanks @bytaesu! - On Cloudflare Workers, consuming a verification value no longer hangs when an earlier request for the same identifier ended mid-query. A superseded code can also no longer be consumed while a newer one is being verified on a database without transactions.#12448
79219b4Thanks @bytaesu! - Schema validation now runs when the auth instance is first used instead of when it is created, so a module-scope instance on Cloudflare Workers no longer logs a validation error on every cold start. On every runtime, a mismatch is no longer logged at startup and is reported by the requests that fail.Updated dependencies [
6536174,262adc0,e9d8e59,79219b4]:v1.7.21Compare Source
Patch Changes
#12126
4739122Thanks @hardikguptaofficialgit! - On Prisma with MongoDB, when your id columns use@db.ObjectId, magic link and email OTP sign-in for users with an unverified email no longer fail with a 500 (Malformed ObjectID). SAML sign-in, DPoP-bound OAuth requests, andprivate_key_jwtclient authentication are fixed the same way, and Sign-In with Ethereum now keeps the email the user supplied instead of a placeholder. Setups with plainStringid columns and other databases are unaffected.#12392
a277020Thanks @gustavovalverde! - Fixed the device approval page showing a valid code as invalid when the same signed-in user looked it up twice at once, such as under React Strict Mode in development.#12052
1c3410aThanks @lakshya-dhariwal! - If you setadvanced.backgroundTasks.handlerand your database adapter runs transactions (the built-in Kysely connection, MongoDB withclient, or Prisma or Drizzle withtransaction: true), email sign-up now keeps the database writes made while sending the verification email or runningonExistingUserSignUp. Before, those writes failed because the sign-up had already been saved. WithemailOTP({ overrideDefaultEmailVerification: true }), the code was never stored, so new users could not verify their email.Whatever adapter or background handler you use, a sign-up verification email now goes out after the new user is saved, and
onExistingUserSignUp, when a duplicate sign-up triggers it, runs after the request's database work completes. IfsendVerificationEmailthrows, the sign-up succeeds and the error is logged; before, the sign-up failed. To reject a sign-up, use adatabaseHooks.user.create.beforehook. IfonExistingUserSignUpthrows, the error is logged and the request still returns the generic response.#12179
9105261Thanks @ghazy2001! - Generic OAuth providers configured withdiscoveryHeadersnow send those headers when fetching the provider's signing keys from ajwks_urion the same origin asdiscoveryUrl. Before, the keys request went out without them. Providers whose key endpoint requires the headers rejected it, so every ID token failed verification and sign-in failed. Ajwks_urion another origin still gets no headers.With
discoveryHeadersset, the discovery request now follows redirects only within the origin ofdiscoveryUrl, so the headers no longer reach another host. If yourdiscoveryUrlredirects to another origin, discovery now fails and Better Auth logs an error. SetdiscoveryUrlto the final URL. Providers withoutdiscoveryHeadersare unaffected.#12401
c3a94c5Thanks @gustavovalverde! - Generic OAuth providers configured withauthorizationHeadersnow send those headers when refreshing an access token, not only when exchanging the authorization code. Before, a provider whose token endpoint requires one of those headers accepted the sign-in but rejected every refresh. Providers withoutauthorizationHeadersare unaffected.refreshAccessTokenandrefreshAccessTokenRequestfrom@better-auth/core/oauth2accept an optionalheadersinput for providers that need extra headers on refresh. Client authentication headers still take precedence over it.Extra headers whose names differ only in case from a client authentication header, such as
Authorizationwithauthentication: "basic", no longer produce a combined, invalid header on the code exchange or refresh. Client authentication replaces them, including headers a customtokenEndpointAuthhook sets in any casing.#12404
9b40ac6Thanks @bytaesu! - Plugin packages no longer import from thebetter-authroot, so bundlers that split code stop emitting unused Kysely chunks when used withbetter-auth/minimal.#12395
b0ced9dThanks @gustavovalverde! - On Cloudflare Workers, sign-ins with agenericOAuthprovider that usesdiscoveryUrl, and OAuth provider requests that use a configured resource (resources), no longer hang. An auth instance created at module scope retries discovery and resource seeding on later requests. Previously, if the request running that retry ended first, every later request that needed it waited forever. Each request now retries on its own until one succeeds.On other runtimes, concurrent requests may send a few more discovery requests or resource lookups while the identity provider or database is unreachable.
#11912
5c12074Thanks @gustavovalverde! - If you use PostgreSQL and other schemas in the same database are dropped while Better Auth reads the database, for example one schema per tenant or parallel test runs, the startup schema check andnpx auth@latest migrateno longer fail intermittently withschema "…" does not existorrelation "…" does not exist. The startup schema check now reads only the schemas on the connection'ssearch_path, plus any schema set withschemaNameor a schema plugin, andmigratereads only the schema it migrates. Before, both read every schema in the database. If no other schemas change while Better Auth reads the database, nothing changes for you.#12403
e64f624Thanks @bytaesu! - SIWE verification no longer includes the internal error message (for example a database error) in its 401 response. The original error is now logged on the server instead.Updated dependencies [
4739122,c3a94c5,4080630,5c12074,b241ffc,57fa843,1a79da5,2d10ae7,ef4d4f7]:v1.7.20Compare Source
Patch Changes
a3f50d0,e9b43d7,265629c,e9b43d7]:v1.7.19Compare Source
Patch Changes
#12323
a10121dThanks @bytaesu! - Fixed theactive-sessionslist in secondary storage being stored without an expiry when the last remaining session had less than a second left.#12335
c41f8ffThanks @bytaesu! - Fixed/admin/create-userleaving a user without a credential account when the password was rejected (for example by the Have I Been Pwned plugin), which made retrying with the same email fail withUSER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.#12338
e786566Thanks @bytaesu! - Fixed/admin/create-userleaving a user without a credential account when creating that account failed, which made retrying with the same email fail withUSER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.#12336
ac778b7Thanks @bytaesu! - Client actions now send the HTTP method passed in the second-argument fetch options, the same as when it is nested underfetchOptions. Passing a lowercasegetorHEADmethod no longer fails because a request body was attached.#12246
5eb69d8Thanks @AryaKesharwani! -/device/tokennow returnsinvalid_clientinstead ofinvalid_grantwhenvalidateClientrejects theclient_id, matching/device/codeand RFC 6749 section 5.2.#12319
4b0327bThanks @bytaesu! - Plugin schema overrides (modelNameand field names) no longer leak into other instances of the same plugin in one process.#12320
2d5ef64Thanks @bytaesu! - Fix OAuth sign-in failing withstate_mismatchduring the daylight saving time fall-back hour on servers that do not run in UTC. The stored OAuth state is no longer created already expired.#12337
5f0a0fcThanks @bytaesu! - The OpenAPI schema now documents query parameters for endpoints whose query schema is wrapped inz.optional(), such as/get-sessionand Stripe's/subscription/list.#12332
89e74b8Thanks @bytaesu! - Fixedorganization.getInvitationreturning a missing, expired, or non-pending invitation error without acode. It now returnsINVITATION_NOT_FOUNDlike the other invitation endpoints.#12318
525bd7aThanks @bytaesu! - FixlistMembersfailing with a syntax error on PostgreSQL and MSSQL when the filter or offset matches no members.#12366
23a60a3Thanks @bytaesu! - Fixed SIWE sign-in leaving a user behind when creating its wallet address or account failed, which blocked that wallet from signing in again.#12340
29e45f8Thanks @bytaesu! - Fixed social sign-up hanging on single-connection databases when the username plugin is enabled and the provider profile maps a username.Updated dependencies [
8412a5f,34ee100]:v1.7.18Compare Source
Patch Changes
#12259
9e2a0ecThanks @gustavovalverde! - Fixed "revoke all sessions" withsecondaryStorageandstoreSessionInDatabaseso that sessions created while the revocation runs are revoked too. Sessions created by asession.delete.afterhook still stay active.#12244
85f4342Thanks @gustavovalverde! -getTestInstancewithtestWith: "mysql"no longer hangs. It expects a MySQL server at127.0.0.1:3306whoserootpassword isroot_password, and it creates and drops a separate database for each instance, so several instances can run in one test file.#12250
278bb28Thanks @gustavovalverde! -getTestInstancefrombetter-auth/testnow closes the Postgres connection pool and the SQLite database it created when the test file finishes. Previously both stayed open until the test worker exited. With Vitest's defaultsequence.hooks: "stack",afterAllhooks in your test files still run before this cleanup and can keep using the instance.Updated dependencies [
9342a73]:v1.7.17Compare Source
Patch Changes
#12181
b6a87adThanks @aliamanatali! - A remembered sign-in or sign-up now refreshes its session like any other remembered session, even if an earlier sign-in in the same browser usedrememberMe: false. Previously the earlier choice stayed in adont_remembercookie until the browser closed, so the new session stopped sliding and expiredexpiresInafter it was created.#12216
cff7c1cThanks @bytaesu! - Denying a device authorization request now rejects a session that was revoked but still present in the cookie cache, matching the behavior of approving a request.#12217
16aa412Thanks @bytaesu! - IP wildcard trusted origins such asexp://192.168.*.*:*no longer match custom-scheme hosts with out-of-range or zero-padded octets like192.168.999.999.#12219
9150314Thanks @bytaesu! - Signing up with an emptyphoneNumberwith the phone number plugin no longer stores the empty string or makes later sign-ups fail. Linking an account whose profile maps an empty phone number no longer replaces the user's verified number.#12212
f5be351Thanks @bytaesu! - Fixed sessions created by asession.delete.afterhook during "revoke all sessions" being treated as revoked immediately whensecondaryStorageis used withstoreSessionInDatabase.#12211
98bcd98Thanks @bytaesu! - Fixed TOTP verification rejecting sessions created from an API key, so API-key sessions can now finish two-factor enrollment.#12228
fbff537Thanks @bytaesu! - Trusted devices are honored again when an app runs the two-factor sign-in step from its own hook on another sign-in path, such as email OTP.Updated dependencies [
fc2e00b]:v1.7.16Compare Source
Patch Changes
#12198
f25081aThanks @bytaesu! -/admin/update-usernow requires the newuser:set-phonepermission to changephoneNumberorphoneNumberVerified. If you use a custom access control, addset-phoneto your statements and grant it to roles that should change phone numbers.#12198
f25081aThanks @bytaesu! - Anonymous users are no longer linked or deleted on a sign-in that is still waiting for a second factor.#12198
f25081aThanks @bytaesu! - Email verification and change email links now only work for the account they were issued to. Opening a change email link no longer signs the user in, and it verifies the new email only if the current one was verified.#12198
f25081aThanks @bytaesu! - Wildcard trusted origins now match custom-scheme URLs by their parsed scheme, host, and path, and no longer match a URL with userinfo or one that cannot be parsed as an origin. A wildcard host made only of digits, dots, and wildcards, such as192.168.*.*, now matches only IPv4 addresses in that range.#12198
f25081aThanks @bytaesu! - Looking up a user by email now returns only an account whose stored address matches the requested one, ignoring case, even on databases with accent-insensitive collations. The email OTP password reset routes now validate the address and send the code to the address on the account.#12198
f25081aThanks @bytaesu! - Email change codes from the email OTP plugin are now bound to the requesting account and the new address. Codes sent before the upgrade must be requested again.#12198
f25081aThanks @bytaesu! - Fixed the email OTPcheckVerificationOtpandresetPasswordendpoints and the phone numberresetPasswordendpoint allowing more OTP guesses thanallowedAttemptswhen requests arrive concurrently. A correct check still leaves the OTP usable for the follow-up call.#12198
f25081aThanks @bytaesu! - The Expo authorization proxy now sets the signed state cookie only for an unexpired state issued by the server.getAuthStateVerificationIdentifieris now exported for plugins that look up OAuth state records.#12198
f25081aThanks @bytaesu! - Passkey registration, device authorization, two-factor enrollment and verification, social account linking, and phone number changes now reject a session revoked in the database or secondary storage, even when cookie caching is enabled. Sessions created from API keys are refused on these routes, except two-factor enrollment and phone number changes.#12198
f25081aThanks @bytaesu! - Generic OAuth providers whose discovery document has ajwks_urinow reject a user profile whosesubdiffers from the ID token'ssub. Direct ID-token sign-in and linking with a generic OAuth provider now resolve the profile from the verified ID token only and ignore a submitted access or refresh token.#12198
f25081aThanks @bytaesu! - Pending organization invitations are now only returned to members whose role can create invitations, and creator-role invitations only to members who hold that role.#12198
f25081aThanks @bytaesu! - Organization invitations use random IDs whenadvanced.database.generateIdis not set, including on adapters with sequential default IDs such as MongoDB. On MongoDB, new invitation IDs are strings instead of ObjectIds.#12198
f25081aThanks @bytaesu! - Dynamic access control roles reused throughhasPermission'suseMemoryCacheoption are now cached per request and per auth instance instead of in a process-global cache, so role changes apply on the next request.#12198
f25081aThanks @bytaesu! - Organization role lists are now trimmed and split on commas wherever they are stored or compared, and invitations with an empty role are rejected.#12198
f25081aThanks @bytaesu! - With the phone number plugin,updateUserInfoOnLinkno longer copies an unverifiedphoneNumberfrom the linked provider's profile. An existing verified number is kept.#12198
f25081aThanks @bytaesu! - WithrequireVerification, phone number password reset now works only for a verified number. No reset code is sent to an unverified number, andresetPasswordreturnsPHONE_NUMBER_PENDING_VERIFICATIONwithout adding or changing a password.#12198
f25081aThanks @bytaesu! - With the phone number plugin, a new user no longer stores aphoneNumberthat was not verified. Sign-up and sign-in routes reject one sent in the request, and a number from a social profile or indatapassed tocreateUseris dropped unless it is marked verified.#12198
f25081aThanks @bytaesu! - Phone number verification and password reset codes are now stored under their own namespaces, so a phone number request can no longer create, consume or delete verification codes that belong to other flows. Codes sent before the upgrade must be requested again.#12198
f25081aThanks @bytaesu! - Account linking and new OAuth users now count an email as verified only when the provider claim istrueor the string"true", which covers generic OAuth, Cognito, and Microsoft Entra ID claims sent as strings. Any otheremailVerifiedvalue, including a non-boolean value returned frommapProfileToUsersuch as1, now reads as unverified.#12198
f25081aThanks @bytaesu! - With secondary storage, revoking all of a user's sessions or deleting the user now also ends sessions missing from the cached session list.Updated dependencies [
f25081a,f25081a,f25081a,f25081a,f25081a]:v1.7.15Compare Source
Patch Changes
#12100
7769e91Thanks @bytaesu! - The admin plugin now rejects banned users when a session is created outside an endpoint, such as throughinternalAdapter.createSession.#12099
3f17193Thanks @bytaesu! - The adminupdate-userendpoint now rejectsdata.idwith a 400 error instead of attempting to change the user's primary key.#12145
804ba8aThanks @bytaesu! - Headers and cookies set by ahooks.beforehook that does not return early now reach the response instead of being dropped. When the endpoint sets the same header or cookie, the endpoint's value wins.#12108
1391673Thanks @bytaesu! - The HubSpot helper ingenericOAuthnow encodes the access token as a single path segment when fetching token info, so tokens containing reserved characters are no longer truncated, and it returnsnullinstead of requesting/access-tokens/undefinedwhen no access token is present.#12142
f63fb0eThanks @bytaesu! - Organization and API key metadata, and OAuth proxy sign-ins whoseadditionalDatacontains aconstructorfield, no longer fail with "Potential prototype pollution attempt detected".#12098
4cdcb1cThanks @bytaesu! - Error messages for a malformedBETTER_AUTH_SECRETSentry now report the entry position instead of including the entry value.#12097
6e7fa27Thanks @bytaesu! - Fixed/sign-in/usernameand/is-username-availablerejecting a username that sign-up accepts whenvalidationOrder.usernameispost-normalization. Both endpoints now validate the same value as sign-up.#12109
264de2bThanks @bytaesu! - Fix the Vue client'suseSession(useFetch)returning no session during Nuxt SSR whenbaseURLis inferred from the environment.Updated dependencies [
9ef0c5b]:v1.7.14Compare Source
Patch Changes
#12031
d2cb7f5Thanks @bytaesu! - Two-factor TOTP now accepts imported Base32 secrets stored asbase32:<secret>.Updated dependencies [
2947ab7]:v1.7.13Compare Source
Patch Changes
#11985
9cb95feThanks @bytaesu! - Type declarations no longer list type-only exports as values, so importing one as a value now fails type checking instead of failing at runtime.Updated dependencies [
2712441,1cf4b2c]:v1.7.12Compare Source
Patch Changes
#11925
fb48619Thanks @bytaesu! - The JWT plugin now verifies JWTs and session cookie-cache JWTs with the algorithm of the stored key instead of the one named in the token header, and logs a warning when it signs or verifies a JWT without an issuer or audience.#11924
681aa5cThanks @bytaesu! -auth.api.getSession()now keeps the original database error aserror.causewhen the session lookup fails, so server code can tell a transient failure from a bug.#11930
b18e3b8Thanks @bytaesu! - OAuth callbacks completed through the OAuth proxy now run the same checks as the standard OAuth callback, including issuer and ID token nonce validation.Updated dependencies [
681aa5c,4bc78ba,876608d,083a743,492134a]:v1.7.11Compare Source
Patch Changes
#10719
bd65db0Thanks @rendermani! - Recognize PostgreSQLint8as a valid number type in migration diffs. Kysely's introspector reportspg_type.typname, so Better Auth's ownbigintcolumns (such as the database-backed rate limiter'slastRequest) came back asint8and were reported as a type mismatch on every run.#11521
37a75c9Thanks @aryan1306! - Fix database-backed rate limiting returning an excessively largeX-Retry-Afterheader instead of the remaining seconds in the rate-limit window.Updated dependencies [
ce96569,2f6ca3f,dfd8dd2]:v1.7.10Compare Source
Patch Changes
#11393
eebd47aThanks @breken-ai! - If you use thelastLoginMethodplugin withoneTap, signing in through Google One Tap now records"google", the same value as signing in with the Google button. Before, a One Tap sign-in recorded nothing: thebetter-auth.last_used_login_methodcookie kept its previous value or stayed unset, and so diduser.lastLoginMethodwithstoreInDatabase.If your
customResolveMethodincludes a/one-tap/callback→"google"workaround, remove that mapping; keep the resolver if it handles other routes.#11821
09d07e6Thanks @gustavovalverde! - The Google One Tap prompt (authClient.oneTap()withoutbutton) no longer callsgetSkippedReason(), which Google's FedCM migration guide says to remove. Before, it called it under anypromptOptions.fedCMsetting.A skipped prompt now ends the flow and calls
onPromptNotificationright away instead of retrying. Google doesn't report why a prompt was skipped, and a prompt the user closed stays hidden for Google's cooldown period, so the retries could not bring it back. Dismissed prompts still retry as before.Google Identity Services still logs its FedCM migration warning, because it does so whenever
prompt()receives a listener. The plugin needs the listener to track dismissals and skips, so the warning is expected and safe to ignore.#11819
eaecb72Thanks @gustavovalverde! - Google One Tap now applies yoursocialProviders.google.mapProfileToUserand customgetUserInfo, the same as the standard Google sign-in. Before, One Tap ignored both and always stored the token'sname,email,email_verified, andpictureclaims.If you configured either one, users created through One Tap now get the fields it returns, including any extra fields such as a first name from
given_name. Returning users are updated from it on each One Tap sign-in whensocialProviders.google.overrideUserInfoOnSignInis on, and so are existing users that One Tap links whenaccount.accountLinking.updateUserInfoOnLinkis on. Check that your mapper suits One Tap users too.If you set
socialProviders.google.overrideUserInfoOnSignIn, One Tap sign-ins now update returning users' name, image, and email from Google, as the redirect sign-in already does.Setups that configure none of these, including
oneTap({ clientId })with no Google provider, are unaffected.#11820
88c9a37Thanks @gustavovalverde! - If you setautoSelectorcancelOnTapOutsideononeTapClient(), the One Tap prompt and the Sign in with Google button now use them. Before, only values passed toauthClient.oneTap()took effect, so withautoSelect: trueon the plugin, returning users still had to pick an account. Values passed tooneTap()still override the plugin's. Apps that set these options only ononeTap(), or not at all, are unaffected.#11696
0cf1f7cThanks @drewsephski! - You can now import the One Tap plugin frombetter-auth/plugins/one-tap, as you can for other plugins. Previously this import failed with a "not exported" error. Imports frombetter-auth/pluginsandbetter-auth/client/pluginsare unchanged.#11822
414e845Thanks @gustavovalverde! - You can now import the Captcha and OpenAPI plugins frombetter-auth/plugins/captchaandbetter-auth/plugins/open-api, as you can for other plugins. Previously these imports failed with a "not exported" error. Imports frombetter-auth/pluginsare unchanged.If your TypeScript config uses
"moduleResolution": "node"(ornode10),better-auth/plugins/multi-sessionandbetter-auth/testnow resolve their types. Previously TypeScript reported that it could not find their declarations. Projects usingbundler,node16, ornodenextresolution are unaffected.Updated dependencies [
fedd055,bbd0a19,99cdfe7]:v1.7.9Compare Source
Patch Changes
#11766
6edaa61Thanks @bytaesu! - AnonAPIError.onErrorhandler that throws or rejects no longer fails the request or causes an unhandled promise rejection. The error is logged and the original error response is returned. Async handlers now finish before the response is sent.Updated dependencies []:
v1.7.8Compare Source
Patch Changes
#11512
4175b34Thanks @bytaesu! - Align the installed@better-auth/utilsversion withbetter-callwhile accepting^0.4.2 || ^0.5.0across official package peers.#11565
b1cfc1eThanks @bytaesu! - Omit non-returned custom fields from inferred output schema types.#11540
678c555Thanks @bytaesu! - Align cookie security attributes with RFC 6265bis.#10591
193efffThanks @kdelay! - Expired or revoked sessions now clear stale cookies when usingcustomSession.#11625
369176eThanks @gustavovalverde! - Fixed single-use checks for apps that setadvanced.database.generateId: "uuid". Default id generation is unaffected, and no migration is needed.private_key_jwtclient authentication, and magic link or email OTP sign-ins that adopt an unverified account failed with aNOT NULLerror on theidcolumn. They now work. SIWE sign-in ignored theemailfield and used the wallet-derived address; it now uses the supplied email when no other account has it.With
generateId: "serial", behavior is unchanged: these checks need string ids, and Better Auth now logs a warning the first time one of these flows runs.#11391
352d04cThanks @breken-ai! - FixcustomSessiontreating session lookup failures as signed-out users. Server-sidegetSessionnow throws an API error, and HTTP clients receive an error response.#11750
e9d9921Thanks @gustavovalverde! - Generic OAuth providers configured withdiscoveryUrlnow recover without a restart when discovery fails at startup. Since 1.7.3, a provider whose discovery request failed at boot (for example, because the identity provider was still starting) was skipped, and every sign-in returnedPROVIDER_NOT_FOUNDuntil the app restarted. Better Auth now retries discovery on the next sign-in, callback, or account-linking request for that provider, and the provider works as soon as discovery succeeds. While discovery keeps failing, sign-in and account linking still returnPROVIDER_NOT_FOUNDand callbacks redirect witherror=oauth_provider_not_found, and each request may wait up to 10 seconds for the retry.#11549
1bd5c37Thanks @bytaesu! - FixlastLoginMethodfailing sign-in whencookieNameormaxAgeis explicitlyundefined.#11174
766cfb3Thanks @Bekacru! - Stop double-decodingcallbackURL,newUserCallbackURLanderrorCallbackURLon magic link verify so percent-encoded characters in callback query params are preserved.#11505
c46992fThanks @bytaesu! - Preserve the referenced model and field keys when generating Kysely migrations, so custom table names that match another model key produce the intended foreign keys.#11718
1d95ebfThanks @bytaesu! - Fix missing device sessions and failed automatic session switching after cache eviction whenstoreSessionInDatabaseis enabled andpreserveSessionInDatabaseis disabled. Automatic switching now follows the order of held sessions, and large lists or sessions whose users no longer exist no longer cause valid sessions to disappear.#10266
c68b2cdThanks @shiminshen! - Projects that enableexactOptionalPropertyTypescan use theoauthProvider()andmcp()plugins again. Since 1.7.0, adding either plugin tobetterAuth()failed to type-check with a TS2322 error.#11637
2627237Thanks @gustavovalverde! - Projects using the organization plugin withskipLibCheck: falseno longer getTS2536: Type '"role"' cannot be used to index type ...fromcrud-members.d.mts. Therolereturned bygetActiveMemberRoleis now typed as the organization's role names directly. Projects withskipLibCheck: true(the default in most templates) were unaffected.#10866
3d1f008Thanks @heliohm! - The organization plugin no longer pulls every zod locale into server bundles.#11626
67743edThanks @gustavovalverde! - Password resets and password changes no longer keep the new password when revoking the user's sessions fails. WithrevokeSessionsOnPasswordResetorrevokeOtherSessions, database sessions are revoked and the password is saved in one transaction when transactions are enabled for the adapter, so a failure leaves the old password and sessions in place. Sessions in secondary storage are still removed after commit, on a best-effort basis. This covers/reset-password, the email OTP and phone number reset endpoints, and/change-password.onPasswordResetnow runs after sessions are revoked, so a throwing callback no longer leaves them active, and a callback that lists the user's sessions no longer sees the revoked ones.#10698
f0ce580Thanks @jashkarangiya! - If you set the phone number plugin'sverifyOTPoption,/phone-number/reset-passwordnow checks the code with it, as/phone-number/verifyalready does. Before, password reset always compared the code with the one Better Auth generated, so apps that verify codes through a provider such as Twilio Verify could not reset passwords. If yoursendPasswordResetOTPsends the code Better Auth generates, make sureverifyOTPaccepts that code. Apps that don't setverifyOTPare unaffected.#11646
07a0938Thanks @gustavovalverde! - A failed password reset no longer spends the reset link or OTP./reset-password,/email-otp/reset-passwordand/phone-number/reset-passwordnow check the token or code without consuming it, validate and hash the new password, then consume it in the same transaction as the session revocation and password write. A rejected password, such as one refused byhaveIBeenPwned, always leaves the link or OTP usable. A later failure, such as a failed session revocation or password write, also leaves it usable when the adapter's transactions are enabled and verification values are stored in the database (the default without secondary storage, orverification.storeInDatabase: true). Wrong codes still count against the OTP attempt limit.#11643
92dfceaThanks @gustavovalverde! - A sign-up that fails after its session is created no longer leaves that session in secondary storage. WithstoreSessionInDatabaseenabled andpreserveSessionInDatabaseoff, the secondary-storage copy is written only after the sign-up commits, and listing a user's sessions now reads the database, so a session whose cached copy failed to write is still listed and revoked. With other secondary-storage settings, sign-up, passkey registration and SSO sign-in withresolveUserkeep the session write inside their transaction, so a storage failure rolls back the new user, passkey or account instead of keeping them without a session. A session revoked by asession.create.afterdatabase hook also stays revoked.#11488
e15625fThanks @Bekacru! - SvelteKit 3 projects can now install Better Auth without--legacy-peer-deps. The@sveltejs/kitpeer range now includes^3.0.0; SvelteKit 2 projects are unaffected.Updated dependencies [
4175b34,435645e,369176e,0cfe07d,9904b29,e78f15a,60525e0,8133265,ea85d13,916ef5c,1d95ebf,6214610,67743ed,c46d2a6,92dfcea,a9f455d,9725f1b]:v1.7.7Compare Source
Patch Changes
#11476
4186e36Thanks @bytaesu! - Return CAPTCHA errors with the correct JSON Content-Type header.#11469
8620aa9Thanks @aryan1306! - Return rate limit errors with a JSON Content-Type header.#11491
55cb92eThanks @bytaesu! - Respect social providerdisableSignUpwhen signing in with an ID token.#11375
69defbcThanks @bytaesu! - Refresh the active organization after sign-in when a session hook selects the initial organization.#11494
ac54bfdThanks @gustavovalverde! - Isolate OAuth state cookies and each OAuth Proxy payload with purpose-specific encryption keys. TheoAuthProxyoptions and supported configuration remain unchanged.Upgrade all Better Auth nodes that handle the same cookie-backed OAuth or SAML relay-state flow together. Upgrade every OAuth Proxy participant, including production and preview or development deployments, in the same cutover. OAuth sign-in, account-linking, and cookie-backed SAML sign-in flows started before the upgrade must be restarted. Mixed old and new participants cannot exchange existing state or proxy payloads, and there is no fallback to the previous shared key.
#11494
ac54bfdThanks @gustavovalverde! - Magic Link verification now accepts only records issued for Magic Link. MagicLink records and database-backed OAuth or SAML state use separate verification
identifier prefixes. Links and database-backed sign-ins started before the
upgrade cannot complete; request new Magic Links and restart those sign-ins.
Upgrade servers sharing verification storage together, and update
verification.storeIdentifier.overridesrules for these flows to match the newmagic-link:andauth-state:prefixes. The link token, callback state,endpoints, and public option types are unchanged.
Upgrade installed Better Auth adapters, plugins, and integrations released
with
better-authalongside it so participating packages use the same releaseversion.
Updated dependencies [
35d7cd3,07bdf7e]:v1.7.6Compare Source
Patch Changes
#11325
af88385Thanks @Wadiou! - Admin pluginbannedUserMessagecan now be a function that receives the banned user, so sign-in errors can include details such as the ban reason.#11268
2fa501cThanks @bytaesu! - Support linking social accounts through the OAuth Proxy plugin.#11366
d41e2caThanks @bytaesu! - Use targeted PRAGMA queries when a Kysely dialect cannot introspect Cloudflare D1.#11016
3d0efa3Thanks @davbrito! - Support Vercel BotID checks on protected authentication routes in Vercel-hosted applications.#11333
631ac29Thanks @bytaesu! - Preserve logical model identity when a custom model name matches another schema key.#11324
8853419Thanks @XXMOHAMED012! - Passwords longer thanmaxPasswordLengthare now rejected withPASSWORD_TOO_LONGbefore hashing on sign-in (email, username, phone number), verify-password, change-password (currentPassword), delete-user, the two-factor endpoints that take a password, and admin create-user, matching what sign-up and password reset already did.#11316
2ee1545Thanks @Smidge! - Fix React hydration mismatches when a session or plugin auth query resolves before a streamed component hydrates. Preserve the server-rendered pending state during hydration, then update to the current client state without changing ordinary or computed plugin stores.#11376
fc45d08Thanks @bytaesu! - Prevent older auth-query responses from replacing newer results when requests overlap.Updated dependencies [
41b7dc1,d41e2ca,631ac29,2b13e01]:v1.7.5Compare Source
Patch Changes
#11283
e56c45bThanks @bytaesu! - Log Cloudflare Turnstile error codes and binding mismatches on the server so CAPTCHA verification failures can be diagnosed.#11209
8d37cc3Thanks @siam923! - Remove the unused optionalbetter-sqlite3peer dependency to prevent installation conflicts.#11272
348fc26Thanks @bytaesu! - Use MySQL's reported byte lengths when validating indexes on existing string columns.#11203
cb627ebThanks @dshukertjr! - Add adatabase.schemaNameoption for direct PostgreSQL connections. When set, the adapter and the CLI qualify every statement with that schema, soauth generatewrites a schema-qualified migration that creates the schema before its tables instead of relying on the connection'ssearch_path.#11270
133f6a2Thanks @bytaesu! - Prevent PostgreSQL migrations from treating tables in other schemas or views in the active schema as Better Auth tables.Updated dependencies [
e18bc83,cb627eb,dae97ed]:v1.7.4Compare Source
Patch Changes
#11205
3f890ebThanks @bytaesu! - Support Vitest 5 in the testing utilities while retaining support for previously supported Vitest versions.#11224
c1756a2Thanks @bytaesu! - Addexperimental.instrumentation.enabledto disable Better Auth OpenTelemetry span creation per auth instance. Instrumentation remains enabled by default and independent of usage reporting.#11217
9b9638eThanks @onmax! - AllowtestUtilsauth helpers to accept additional session fields through thesessionoption, including required fields without defaults and per-session overrides of configured defaults.Updated dependencies [
3ff842a,b905bfe,c1756a2]:v1.7.3Compare Source
Patch Changes
#11060
3660f06Thanks @bytaesu! - Handle malformed custom-scheme callback URLs without excessive processing.#11037
5bd7096Thanks @bytaesu! - Prevent repeated TOTP enrollment from replacing an active authenticator and its backup codes.#11120
7ec7146Thanks @onmax! - PreventgetSessionfrom failing when cookie caching is disabled while clients still have cached session cookies.#9908
76d311fThanks @harshil1712! - Add Cloudflare as a built-in social provider, with support for client-secret authentication and PKCE clients without a secret.#11188
c47b765Thanks @bytaesu! - Normalize Auth0 domains without a potentially slow trailing-slash regular expression.#11084
2d5c63dThanks @bytaesu! - Prevent duplicate session requests and hydration mismatches when using the Vue client with NuxtuseFetch.#11147
a9d8c12Thanks @bytaesu! - AddisPasswordCompromisedfor checking passwords against Have I Been Pwned in custom server-side flows, while ignoring padded response entries with zero occurrences.#10988
9fc7498Thanks @bytaesu! - Run callback hooks after proxied OAuth sign-ins and preserve server state when callback cookies are unavailable. The legacy/oauth-proxy-callbackendpoint is deprecated and will be removed in the next minor release.#11178
be0e007Thanks @bytaesu! - Report missing tables, missing columns, and required columns Better Auth never writes during initialization, with guidance for fixing them. Kysely checks the live database schema. Authentication requests await the same check and are rejected if the schema does not match.Validation is enabled by default, including in production. Set
advanced.database.validateSchema: falseto disable runtime validation.auth migraterefuses to apply changes when required unwritten columns need manual repair.#11069
0bb0dbfThanks @bytaesu! - Improve dynamic organization role permission check performance.#11153
2220ee7Thanks @bytaesu! - Restore sign-in compatibility with 1.6 databases by identifying accounts with(providerId, accountId)and removing theissuerrequirement introduced in 1.7.0. Upgrading from 1.6 no longer requires an account schema migration. Ambiguous account keys are rejected instead of selecting an arbitrary account.If you applied the 1.7.0 through 1.7.2 account schema, remove its issuer unique index before upgrading. For SQL databases, also make
issuernullable or remove the column so sign-ups and account linking can succeed.auth migratedoes not perform this cleanup. Follow the upgrade guide for database-specific steps.#10978
5fe5bc2Thanks @BetterAndBetterII! - Skip a generic OAuth provider when discovery fails instead of taking down the rest of the auth API.#10963
74a7369Thanks @thisismert! - Track email OTP sign-ins in the last login method plugin.#11085
e16b40aThanks @bytaesu! - Provide type-safe NuxtuseFetchintegration for the Vue client'suseSessionhook.#11066
c0444dcThanks @bytaesu! - Upgrade the packaged Zod dependency to 4.5. Generated OpenAPI schemas now mark required request fields consistently with runtime validation, including passkey registration responses.Updated dependencies [
352d012,76d311f,3e9e197,157ec8d,baa08f4,9e36635,be0e007,a2bae0c,1a1b7d5,2220ee7]:v1.7.2Compare Source
Patch Changes
#12603
ad07eddThanks @gustavovalverde! - An error thrown from an endpoint no longer drops theAccess-Control-Expose-Headersnames that a hook or the endpoint set earlier. The names from both are kept. Before, the error's value replaced the list, so a browser client on another origin could lose access to headers a hook had exposed.Updated dependencies [
69e14d8,2a8750b,75c0821]:v1.7.1Patch Changes
#12323
a10121dThanks @bytaesu! - Fixed theactive-sessionslist in secondary storage being stored without an expiry when the last remaining session had less than a second left.#12335
c41f8ffThanks @bytaesu! - Fixed/admin/create-userleaving a user without a credential account when the password was rejected (for example by the Have I Been Pwned plugin), which made retrying with the same email fail withUSER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.#12338
e786566Thanks @bytaesu! - Fixed/admin/create-userleaving a user without a credential account when creating that account failed, which made retrying with the same email fail withUSER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.#12336
ac778b7Thanks @bytaesu! - Client actions now send the HTTP method passed in the second-argument fetch options, the same as when it is nested underfetchOptions. Passing a lowercasegetorHEADmethod no longer fails because a request body was attached.#12246
5eb69d8Thanks @AryaKesharwani! -/device/tokennow returnsinvalid_clientinstead ofinvalid_grantwhenvalidateClientrejects theclient_id, matching/device/codeand RFC 6749 section 5.2.#12319
4b0327bThanks @bytaesu! - Plugin schema overrides (modelNameand field names) no longer leak into other instances of the same plugin in one process.#12320
2d5ef64Thanks @bytaesu! - Fix OAuth sign-in failing withstate_mismatchduring the daylight saving time fall-back hour on servers that do not run in UTC. The stored OAuth state is no longer created already expired.#12337
5f0a0fcThanks @bytaesu! - The OpenAPI schema now documents query parameters for endpoints whose query schema is wrapped inz.optional(), such as/get-sessionand Stripe's/subscription/list.#12332
89e74b8Thanks @bytaesu! - Fixedorganization.getInvitationreturning a missing, expired, or non-pending invitation error without acode. It now returnsINVITATION_NOT_FOUNDlike the other invitation endpoints.#12318
525bd7aThanks @bytaesu! - FixlistMembersfailing with a syntax error on PostgreSQL and MSSQL when the filter or offset matches no members.#12366
23a60a3Thanks @bytaesu! - Fixed SIWE sign-in leaving a user behind when creating its wallet address or account failed, which blocked that wallet from signing in again.#12340
29e45f8Thanks @bytaesu! - Fixed social sign-up hanging on single-connection databases when the username plugin is enabled and the provider profile maps a username.Updated dependencies [
8412a5f,34ee100]:v1.7.0Minor Changes
#8733
4e8e4c7Thanks @bytaesu! - AddhydrateSessionto seed the client with a server-fetched session souseSessionreturns data on the first render.#9930
0cbaf81Thanks @gustavovalverde! - Anonymous account linking now works after social and generic OAuth sign-in in Expo and other in-app browsers, where the OAuth callback returns without the session cookie.onLinkAccountfires and the anonymous user is migrated; before, it was silently skipped.Plugins can now carry server-trusted data across an OAuth redirect with the new
addOAuthServerContextAPI, read back on the callback viagetOAuthState().serverContext. UnlikeadditionalData, it cannot be set from the request body, so it is the right place for values the server must trust.For
@better-auth/oauth-provider, the post-login authorization query now travels through that server-only channel, so it can no longer be injected throughadditionalData.#10004
b36c38fThanks @bytaesu! - The captcha plugin now requires endpoint entries to match full auth paths unless they use wildcard patterns. This prevents requests like/sign-in//emailfrom bypassing captcha while preserving trailing-slash matches like/sign-in/email/. To protect multiple routes, replace partial paths like/sign-inwith explicit wildcards such as/sign-in/*or/sign-in/**.#10746
6782647Thanks @gustavovalverde! - OAuth device grants now useoauthDeviceAuthorization()alongsideoauthProvider()ormcp(). This single integration replaces both the standalonedeviceCodeGrant()plugin and the shared-grant configuration. Standalone Device Authorization no longer accepts or stores RFC 8707 resources, andonDeviceAuthRequestreceives onlyclientIdandscope. The OAuth integration rejects resource indicators that are not absolute, fragment-free URIs.The OAuth integration replaces the optional
resourcecolumn withoauthClientIdandresources. Regenerate and apply the schema when using it. Before upgrading from an earlier 1.7 prerelease, let pending OAuth device codes expire or delete them because they cannot be exchanged through the new integration.#10402
763a267Thanks @gustavovalverde! - Plugin database schemas can now define named or generated table-level indexes across multiple fields. SQL migrations and generated Drizzle or Prisma schemas resolve configured table and column names consistently, while the MongoDB adapter creates the same indexes before the first index-enforcing write.#9766
bf39cbfThanks @GautamBytes! - Add a server-onlyauth.api.consumePhoneNumberOTPAPI for custom phone OTP flows that need to verify and consume a code without creating or updating users or sessions.#10330
081d3c3Thanks @ping-maxwell! - Allow the username plugin's separatedisplayUsernamefield to be omitted bysetting
displayUsername: falseon both the server and client plugins.#10059
49b5cf6Thanks @GautamBytes! - Device Authorization now creates unique database indexes fordeviceCodeanduserCode, so each generated code must be unique in its column. Existing installations on every adapter must resolve duplicate values before applying the migration. MySQL and SQL Server installations must also convert both columns to bounded strings and clean up values longer than 191 characters before running it.Generated codes are limited to 191 characters. Issuance makes up to 3 attempts to overcome unique-key collisions, then returns
server_errorif it cannot create a uniquedeviceCodeanduserCode. Default-generated user codes accept case changes and readability separators during verification, approval, and denial; custom codes outside the default alphabet are matched exactly. The/devicelimiter allows 5 requests over a window equal to the configured code lifetime, while/device/tokenpolling keeps its separate interval behavior.#9645
e014029Thanks @ping-maxwell! - Harden the Electron OAuth flow and tighten custom-scheme trusted-origin matching.The Electron sign-in flow now mandates PKCE S256. Plain PKCE is rejected: the
code_challenge_methodparameter is gone and every authorization code is verified by hashing the verifier with SHA-256. The server no longer trusts anelectron-originheader to set the request Origin. The Electron client now sends a realOrigin(for examplemyapp:/), so upgrade the@better-auth/electronclient and server together and make sure your app's scheme is intrustedOrigins. The unuseddisableOriginOverrideoption is removed.Custom-scheme entries in
trustedOriginsnow match by scheme and authority instead of string prefix. A host-less entry such asmyapp://orexp://still trusts every host of that scheme, but a host-bearing entry such asmyapp://callbackmatches that host exactly, so it is no longer satisfied bymyapp://callback.attacker.tld.#9948
3d04fabThanks @yordis! - feat(generic-oauth): addrefreshTokenParamsconfig to forward extra params on token refreshMulti-tenant OIDC providers (Zitadel multi-org, Auth0 with
audience) need to send extra body params on the refresh call to rescope tokens without a full authorization redirect. The generic-oauth plugin now accepts arefreshTokenParamsoption (object or sync/async function) that is merged into the refresh request body, withgrant_typeandrefresh_tokenprotected from override. The function form receives request metadata for the request that triggered the refresh, so request-scoped data (headers, cookies) is available without out-of-band state like AsyncLocalStorage.UpstreamProvider.refreshAccessTokennow accepts an optional secondctxargument; the change is backwards compatible because existing implementations that take onlyrefreshTokenremain valid. See #7554.#9069
c7d2253Thanks @gustavovalverde! - Rewrite the generic OAuth plugin as a first-class social provider with OAuth 2.1 security defaults. Providers now usesignIn.social+callback/:idinstead of dedicated plugin endpoints, with PKCE required by default (OAuth 2.1), RFC 9207 issuer validation, OIDC auto-discovery withopenidscope injection, and typed provider IDs.Breaking changes:
signIn.oauth2({ providerId })replaced bysignIn.social({ provider })oauth2.link()replaced bylinkSocial()/api/auth/oauth2/callback/:idto/api/auth/callback/:idgenericOAuthClient()removed; generic OAuth providers now use the standard social client APIspkcedefaults totrue(wasfalse); setpkce: falsefor providers that reject PKCEauthorizationUrlParamsandtokenUrlParamsonly acceptRecord<string, string>issuerandrequireIssuerValidationconfig fields removed; issuer validation is automatic via OIDC discoverymapProfileToUserprofile typed asOAuth2UserInfo & Record<string, unknown>#9966
ec8a38cThanks @gustavovalverde! - genericOAuth providers configured with adiscoveryUrlnow verify the provider'sid_tokenagainst its published JWKS (signature, issuer, audience, and advertised algorithms) and bind it to the authorization request with a server-generated OIDCnonce. A sign-in whoseid_tokenfails verification, or does not echo the expectednonce, is rejected.Set
disableIdTokenNonceBinding: trueon a provider that does not return thenonceclaim in the authorization-code flow.These providers also accept client-submitted id_token sign-in through
signIn.social({ idToken }), which previously returnedID_TOKEN_NOT_SUPPORTED.Providers configured with explicit endpoints instead of
discoveryUrlare unchanged.#9368
430c895Thanks @GautamBytes! - Generic OAuth users can now sign out from the configured OpenID provider when they callauthClient.signOut(). When a provider exposes a discovered or configured logout endpoint, Better Auth redirects to it and includes the storedid_token_hintwhen available. PasscallbackURLor configurepostLogoutRedirectURIfor the return flow, with optionalstate, or setdisableRedirectto handle the returnedurlyourself. When multiple linked providers support logout, Better Auth selects the most recently updated account. SetdisableProviderLogout: trueto keep sign-out local.#9431
523f95cThanks @pi0! - feat: makeAuthinstance fetchable#10577
5c45abcThanks @gustavovalverde! - MCP clients that hit a scope wall now learn exactly which scopes to ask for. Missing protected scopes produce a403with an RFC 6750insufficient_scopeWWW-Authenticatechallenge that names every missing scope. Clients can union those scopes into one authorization request instead of opening one browser redirect per scope.requiredScopesthroughRequireMcpAuthOptionsor the matchingcreateMcpProtectedRequestHandlerverifier option. Exact membership remains the default;isScopeSatisfiedcan define hierarchical policies.createInsufficientScopeErrorwhen an operation determines its required scopes dynamically.createResourceServerChallengeconverts that signal and recognized token failures into safe RFC 6750 challenges.challengeScopesonly as the unauthenticated challenge hint.Handler-produced responses, ordinary permission denials, configuration failures, and unrelated thrown values keep their original status and identity.
#10403
dbd302eThanks @gustavovalverde! - Scope account identity by trusted issuer instead of provider configuration. Accounts now use the unique(issuer, accountId)key, so aliases for one OpenID Connect issuer deduplicate one external identity while equal subjects from different issuers remain separate. This identity deduplication does not introduce independent grant or provider lifecycle records for aliases.This release requires
Account.issuerbut preservesAccount.accountIdas the provider-assigned account identifier. Account-specific APIs select the localAccount.idthrough theaccountIdrequest property; token and provider-profile APIs can instead select the signed account cookie withuseAccountCookie: true. Credential accounts uselocal:credentialand the linked user's stableidas their provider identity.OAuth provider identity now comes from raw verified profiles. OpenID Connect discovery uses
sub, plain OAuth usesid, and providers can declareaccountSubjectfor another immutable field; Better Auth no longer switches betweensubandidat runtime.getUserInfo().userno longer carries provider identity, andmapProfileToUsercannot returnid. Read the selected identity fromaccountInfo.account.accountIdinstead ofaccountInfo.user.id. The genericmicrosoftEntraIdhelper now requires a concrete tenant GUID; use the built-in Microsoft provider for multi-tenant authorities.SSO account subjects are now protocol-defined. OIDC uses the verified
subclaim, and SAML uses the signedNameID;mapping.idis removed from both configurations. A manual SAML configuration without metadata XML must setidpMetadata.entityID, becausesamlConfig.issueridentifies the service provider and no longer acts as the IdP identity.Apply the reviewed account-identity backfill in the Better Auth 1.7 upgrade guide before deploying. The generated schema migration cannot assign trusted issuers or resolve existing identity collisions automatically.
#10359
8784c1cThanks @ping-maxwell! - Database joins have moved out ofexperimentalinto a stable option atadvanced.database.joins(default:false).If you previously set
experimental: { joins: true }, update your config to:Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (
npx auth@latest generate).#9992
e53582cThanks @gustavovalverde! - The MCP plugin moves out ofbetter-authinto its own package,@better-auth/mcp, built on@better-auth/oauth-provider. Import the authorization plugin and protected-request helpers from the package root. The in-core MCP client (createMcpAuthClientand its adapters) is removed; MCP protocol and transport clients come from the official version 2@modelcontextprotocol/clientand@modelcontextprotocol/serverpackages. The OAuth endpoints move from/mcp/*to/oauth2/*, with discovery at/.well-known/oauth-authorization-serverand protected resource metadata at/.well-known/oauth-protected-resource. Discovery-based MCP clients pick up the new locations on their own.The shared-auth route helper is renamed from
withMcpAuthtorequireMcpAuth. The standalone protected-resource factory is renamed frommcpHandlertocreateMcpProtectedRequestHandler; pass one flatMcpProtectedRequestHandlerOptionsobject withissuer, a singleaudience, optionaljwtVerifyOptions, token-verification fields, and challenge fields. Its callback receivesaccessTokenClaims.requireMcpAuthverifies the access token against the published JWKS, validates DPoP proofs for DPoP-bound tokens, and passes the verified access-token claims to your handler.createInsufficientScopeErrornow validates a custom description against the RFC 6750error_descriptioncharacter set when the error is constructed. Invalid descriptions throwTypeError("invalid error_description")before an error can reach resource-challenge serialization.MCP 2026-07-28 uses a stateless request and response transport. Serve MCP routes with version 2 of
@modelcontextprotocol/server, configurecreateMcpHandlerwithlegacy: "reject", wrap it withrequireMcpAuth, and export onlyPOST. Remove MCP-routeGETandDELETEexports and session-store options such asredisUrl. OAuth clients, consent, authorization codes, refresh tokens, and security records remain durable authorization state.To migrate, install
@better-auth/mcp,@better-auth/cimd, and the official version 2 MCP client or server package needed by your application; add thejwt()plugin, which is now required for token signing; and move options that were nested underoidcConfigto flat options onmcp({ ... }). The database models change:oauthApplicationbecomesoauthClient, with newoauthRefreshTokenandoauthClientAssertiontables. Regenerate or migrate your schema withnpx auth migrateornpx auth generate.#10204
0683a5fThanks @GautamBytes! - Microsoft sign-in now identifies Entra accounts with the stableoidclaim in both the built-inmicrosoftprovider and the Generic OAuthmicrosoftEntraIdhelper. Tokens without a validoidare rejected, and the Generic OAuth helper refuses to initialize unless Microsoft discovery provides ID-token verification metadata. Existing Microsoft account rows created fromsubmust be migrated before upgrading.#9305
e7eb45bThanks @gustavovalverde! - feat(oauth): per-requestadditionalParamsandloginHintparity acrosssignIn.social,linkSocial, andsignIn.ssoUnified escape hatch for customizing the provider authorization URL on a per-request basis. Previously, dynamic parameters like Google's
access_type=offline/prompt=consent, Cognito'sidentity_provider=Google, or Microsoft'sdomain_hintcould only be set as static server configuration.New capabilities
signIn.social,linkSocial, andsignIn.ssoacceptadditionalParams: Record<string, string>. Values are appended to the authorization URL as query parameters.linkSocialalso acceptsloginHint, matching the surface ofsignIn.socialandsignIn.sso.OAuthProvider.createAuthorizationURLgainsadditionalParamsin its input contract; every built-in provider forwards it to the shared helper.additionalParamswith the config-levelauthorizationUrlParams; call-time wins on key collision.identityProvider?: stringconfig option that maps to theidentity_providerquery parameter, avoiding magic strings.Security
createAuthorizationURLhelper silently drops any caller-supplied key inRESERVED_AUTHORIZATION_PARAMS(state,client_id,redirect_uri,response_type,code_challenge,code_challenge_method,nonce,scope). The request-body Zod schema rejects the same keys with 400, so misuse is visible at the edge rather than silently overriding security-critical parameters.nonceis reserved so a caller cannot replace the OIDC nonce Better Auth generates when binding a discovery provider'sid_tokento the authorization request.wechat→appid,tiktok→client_key) additionally filter those keys so a caller cannot swap the configured OAuth app.atlassian→audience,notion→owner) are merged last so caller-suppliedadditionalParamscannot override them. Configured defaults that represent operator intent (e.g. Googleinclude_granted_scopes, CognitoidentityProvider) remain caller-overridable.signIn.ssorejectsadditionalParamswith 400 when the resolved provider is SAML; the SAML AuthnRequest is signed and cannot carry caller-supplied query parameters, so silently dropping them would mislead integrators.OpenAPI
ZodRecordhandling to the OpenAPI generator soz.record()fields emittype: objectwith typedadditionalProperties. Incidentally fixes a long-standing bug whereadditionalDatawas rendered astype: string.Refactors
discord,roblox,zoom, andslackproviders now delegate to the sharedcreateAuthorizationURLhelper and inherit its RFC behavior and reserved-key guard.tiktokandwechatkeep their manual URL construction (non-standard OAuth2 parameter names and URL fragment requirements) but threadadditionalParamswith the same reserved-key filter.Closes #2351.
Closes #5441.
Closes #5592.
Closes #5604.
Supersedes #4992 and #5443.
#10127
7c7313cThanks @gustavovalverde! - OAuth sign-in, account linking, callback, and proxy flows now buildredirect_urifrom the current request base URL whenbaseURL.allowedHostsis configured. Built-in social providers and generic OAuth providers now use the resolved request host for redirects in multi-host deployments.Custom
OAuthProviderimplementations can omitcallbackPathwhen using the shared/callback/<provider-id>route. SetcallbackPathonly for custom callback routes.#10039
aedcb97Thanks @gustavovalverde! - feat(oauth-provider)!: DPoP-bound access tokens (RFC 9449)OAuth provider integrations can issue and verify DPoP sender-constrained tokens. Clients request them with
dpop_bound_access_tokensat registration,dpop_jkton the authorization request, or by targeting a resource configured withdpopBoundAccessTokensRequired. Issued tokens carrycnf.jkt, returntoken_type: "DPoP", and stay bound through refresh-token rotation, introspection, and userinfo.Resource servers verify DPoP requests with
verifyAccessTokenRequest, which checks theAuthorization: DPoPscheme, the proof, the request target, the access-token hash, and proof replay. The MCP package advertises DPoP in protected resource metadata and verifies DPoP-bound requests. Proof replay is rejected through the database-backed verification store, so anti-replay holds across instances.verifyAccessTokenRequestandrequireMcpAuthuse that store by default; build one withcreateDpopReplayStore(internalAdapter)or pass a customdpop.replayStore. This needs database-backed verification storage: a secondary-storage-only deployment rejects DPoP requests rather than skipping replay protection.Breaking: the raw-token verifier
verifyAccessTokenis renamed toverifyBearerToken, both inbetter-auth/oauth2and as theoauthProviderResourceClientaction, and it rejects DPoP-bound tokens. UseverifyAccessTokenRequeston any endpoint that may receive them. The resource-request input type is renamed fromAccessTokenRequestInputtoResourceRequestInput, and the DPoP algorithm option issigningAlgorithmseverywhere.Run a schema migration for the DPoP token-binding fields: the
confirmationcolumn on the access-token and refresh-token tables. DPoP-bound clients also gaindpopBoundAccessTokensand resourcesdpopBoundAccessTokensRequired. No dedicated replay table is added; proof replay reuses the verification store.#9828
4f53b61Thanks @gustavovalverde! - Verify social-provider id_tokens with a single shared verifier.Client-submitted id_token sign-in (
signIn.social({ idToken })and account linking) is verified by one function instead of a per-providerverifyIdTokenmethod. Each provider declares anidTokenconfig with a JWKS source, issuer, and audience, and the core verifier runs the signature, issuer, audience, and nonce checks. A provider that declares no config rejects the client id_token path.PayPal previously accepted any decodable id_token without verifying its signature. PayPal derives identity from the access token, so it now declares no
idTokenconfig, and the client id_token path returnsID_TOKEN_NOT_SUPPORTED. PayPal sign-in through the redirect flow is unchanged.Custom providers that implement
UpstreamProviderdirectly replace the removedverifyIdTokenmethod with anidTokenconfig:For verification that cannot use a local JWKS, pass
idToken: { verify: async (token, nonce) => boolean }. TheverifyIdTokenanddisableIdTokenSignInprovider options are unchanged.#9079
6f2948eThanks @gustavovalverde! - feat(oauth-provider): computeat_hashin ID tokens per OIDC Core §3.1.3.6ID tokens issued alongside an access token now include the
at_hashclaim, which cryptographically binds the two tokens to prevent token substitution attacks. The hash algorithm is selected based on the actual signing key's algorithm (EdDSA/Ed25519 uses SHA-512, RS/ES/PS384 uses SHA-384, RS/ES/PS512 uses SHA-512, all others use SHA-256).A new
resolveSigningKey()export is available frombetter-auth/pluginsto resolve the current JWKS signing key (including its algorithm). When using a customjwt.signcallback, the signed ID token's header is validated against the declared algorithm to preventat_hashmismatches.#10135
f68044dThanks @brentmitchell25! - Registered OAuth clients can now use the RFC 8628 device flow to obtain OAuth access tokens. AddoauthDeviceAuthorization()alongsideoauthProvider()ormcp(), request a code at/device/code, and exchange it at/oauth2/tokenafter the user approves it. OAuth and OpenID discovery advertise thedevice_authorization_endpoint.Device authorization requests can bind RFC 8707 resource indicators.
GET /devicereturns the requested client, scopes, and resources to the authenticated user who owns the request. Token requests can reuse or narrow the approved resources, but cannot add new ones. Existing first-party device clients continue to receive Better Auth session tokens from/device/token.Enabling
oauthDeviceAuthorization()adds nullableoauthClientIdandresourcesfields todeviceCode. Regenerate and apply the database schema after adding the integration.Confidential clients authenticate at
/device/codewith their registered method, while public clients sendclient_id. Emptyclient_id,scope,user_id, and authentication values are treated as omitted; multiple non-empty values for any of these parameters returninvalid_request, while multipleresourcevalues remain supported. Unknown OAuth client IDs enter the standalone device flow only whenoauthDeviceAuthorization({ validateClient })accepts them.#9929
91f235fThanks @gustavovalverde! - AddrequireEmailVerificationto OAuth provider options, for built-in social providers and the Generic OAuth plugin. When a provider reports an unverified email, the user and account are still created or linked, but no session is issued: the OAuth callback redirects with?error=email_not_verified, and ID token and One Tap sign-in return403EMAIL_NOT_VERIFIED. Verification emails follow the existingemailVerification.sendOnSignUp/sendOnSignInsettings.It is opt-in per provider and does not inherit
emailAndPassword.requireEmailVerification, so existing social logins keep working. The gate checks the local user's verification state, so a user verified through another method keeps access. Only enable it for providers that report a trustworthyemail_verifiedsignal.#9648
d2a79baThanks @brentmitchell25! - OAuth provider now models protected resources explicitly. Configure them withresourcesor create them through theoauthResourceadmin API. Each resource can define token TTLs, allowed scopes, custom JWT claims, and JWT signing pins.validAudiencesis removed. Move each existing resource identifier intoresources; link clients that should be limited to specific resources throughoauthClientResourceor Dynamic Client Registrationresources.Access-token issuance now applies resource policy to the requested RFC 8707
resourcevalues. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emitsjti, and keeps repeatedresourceform parameters.Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource
refreshTokenTtllonger thanrefreshTokenExpiresInwill see refresh tokens expire at the provider default instead of the longer resource value.JWT signing can now honor per-resource pins.
signJWT()acceptssigningKeyIdandsigningAlgorithm; JWKS adapters exposegetKeyById()andgetLatestKeyByAlg(). Thejwkstable adds nullablealgandcrvcolumns, andkeyPairConfigscan provision multiple algorithms in one keyring.After upgrading, run
npx auth generateand apply the migration before deploying. The migration addsoauthResource,oauthClientResource, and the newjwkscolumns. Without it, resources usingsigningAlgorithmcannot find matching keys.Resource servers should publish RFC 9728 protected-resource metadata at their own origin. The OAuth provider exposes challenge helpers that point clients at that metadata.
@better-auth/mcpnow requires an explicitresourceoption. The plugin stores that identifier as an OAuth resource, publishes RFC 9728 protected-resource metadata for it, and binds issued access tokens to that resource. Existingmcp({ loginPage, consentPage })setups should add a protected MCP resource identifier, for exampleresource: "https://api.example.com/mcp".#10397
bb6c102Thanks @ping-maxwell! - Addorganization.getOrganization()to fetch organization metadata without members or invitations.#8931
34558bcThanks @GautamBytes! - Add opt-in JWKS-backed asymmetric JWT support forsession_datacookie cache tokens, so services can verify cookie-cache JWTs with public keys instead of shared secrets. Enable it withjwt({ sessionCookieCache: true })alongsidesession.cookieCache.strategy = "jwt".#8977
954b664Thanks @ruban-s! - allow passinguserIdandorganizationIdto thelistUserTeamsAPI.userIdlets callers list teams for another member of an organization (gated behind themember:updatepermission).organizationIdscopes the result to a specific organization without needing to switch the session's active organization, matching the pattern used byaddTeamMember/removeTeamMember.#9969
76a3342Thanks @gustavovalverde! - Signing out withsecondaryStorageandsession.preserveSessionInDatabasenow runs your configuredsession.deletehooks and marks the preserved session row as ended. OAuth Provider access and refresh tokens bound to that session are revoked and back-channel logout is dispatched on sign-out. Previously these hooks were skipped in this setup, so the tokens stayed valid until they expired.#9657
1e5b808Thanks @gustavovalverde! - Hardenprivate_key_jwtand token endpoint client authentication, and add the helpers that make the fix structural.@better-auth/core/oauth2now exposesencodeBasicCredentialsanddecodeBasicCredentials, a round-trip-tested pair that follows RFC 6749 §2.3.1 (application/x-www-form-urlencodedeach value, split on the first:only). The decoder accepts the scheme case-insensitively and tolerates one or more spaces before the credentials per RFC 7235 §2.1.client_secret_basicon the client side and the Better Auth OAuth provider on the server side both go through these helpers, so credentials containing reserved characters round-trip cleanly across the stack and headers likebasic xxxorBasic xxxare accepted.createPrivateKeyJwtClientAssertionGettervalidates options eagerly. Unsupported algorithms (HS256,none), a JWK with no key material, and disagreement between an explicitalgorithmand the JWK-embeddedalgall throw at construction rather than on the first token request.signPrivateKeyJwtClientAssertionenforces the same checks for direct callers. Breaking: configurations that paired an unsupported JWKalgwith a different explicitalgorithmused to silently sign with the explicit option; they now fail at construction.Breaking:
@better-auth/oauth-provideraccepts clientjwksmetadata only as an RFC 7517 JWK Set object with a non-emptykeysarray. Replacejwks: [key]withjwks: { keys: [key] }in DCR payloads, administrative and user client creation, Client ID Metadata Documents, test fixtures, and generated client code. Remotely fetchedjwks_uriresponses must use the same object shape. EC keys must use P-256, P-384, or P-521; OKP keys must useEd25519. When a key declaresalg, it must be a supportedprivate_key_jwtalgorithm that matches the key type and curve; omitalgwhen the client chooses the algorithm in its assertion header. OAuth client rows previously written throughoauthToSchemaare already stored as JWK Set objects, so this is a request, configuration, and type migration rather than another database rewrite; audit rows written outside Better Auth separately.The SSO
private_key_jwtflow redirects witherror_description=no_private_key_availablewhen aresolvePrivateKeycallback returns noprivateKeyJwkorprivateKeyPem. The redirect path previously short-circuited only when the resolver was absent entirely; an empty resolver return fell through into an internal signing error.better-auth/testaddsgetHttpTestInstance, a counterpart togetTestInstancethat binds a real HTTP listener on an OS-assigned port and constructs the auth instance against the discovered URL. It removes the temp-server-then-rebind race that test files have been individually copy-pasting.#8836
93d3871Thanks @gustavovalverde! - Add client authentication configuration for token endpoint requests across the stack, includingprivate_key_jwt(RFC 7523).Generic OAuth providers now accept
tokenEndpointAuthfor token endpoint client authentication. UsetokenEndpointAuth: { method: "private_key_jwt", getClientAssertion }for JWT client assertions,{ method: "none" }for public clients, and{ method: "client_secret_basic" }or{ method: "client_secret_post" }withclientSecretfor explicit secret-based client authentication. The existingauthentication: "basic" | "post"option remains available for secret-based token requests.Use
createPrivateKeyJwtClientAssertionGetter()to sign RFC 7523 assertions from a private key. The assertion getter receives{ clientId, tokenEndpoint, grantType }, so integrations do not duplicate client ID or token endpoint values inside assertion helpers. Core OAuth2 now exports private-key JWT-specific helpers and types:signPrivateKeyJwtClientAssertion,createPrivateKeyJwtClientAssertionGetter,PrivateKeyJwtSigningAlgorithm, andPRIVATE_KEY_JWT_SIGNING_ALGORITHMS.Token endpoint client authentication parameters are derived from
clientId,clientSecret, andtokenEndpointAuth. Configured token endpoint authentication requiresclientId; secret-based token endpoint authentication also requiresclientSecret. Custom token parameters are for provider-specific fields and do not replace the configured client authentication values.refreshAccessToken()now forwardsresourcevalues to refresh-token requests, so RFC 8707 resource indicators work through both the high-level refresh helper andrefreshAccessTokenRequest().The synchronous OAuth2 request builders
createAuthorizationCodeRequest,createRefreshAccessTokenRequest, andcreateClientCredentialsTokenRequesthave been removed. Use the asyncauthorizationCodeRequest,refreshAccessTokenRequest, andclientCredentialsTokenRequesthelpers instead.Servers verify JWT client assertions signed with asymmetric keys, and clients can use the same token endpoint authentication contract for authorization code, refresh, and client credentials token requests.
#9134
652fa53Thanks @gustavovalverde! - The dynamicbaseURLconfig now ignoresx-forwarded-hostandx-forwarded-protounless you setadvanced.trustedProxyHeaders: true.Requests using
baseURL: { allowedHosts }now resolve the auth origin fromHostby default, so forwarded headers cannot select another allowed host unless trusted proxy headers are enabled.Breaking change: if your proxy exposes the public hostname only through
x-forwarded-host, setadvanced.trustedProxyHeaders: true. Deployments where the proxy rewritesHostto the public hostname (nginx default, Vercel, Cloudflare, and Netlify) are unaffected.Migration:
#9240
729c00dThanks @adrianmxb! - feat(username): add immutable username optionThis allows users to set their username during sign-up or first update, but prevents changing it to a different value afterwards. Users can still update other profile fields.
#10031
6fe9faaThanks @gustavovalverde! - Remove the deprecatedoidcProviderplugin frombetter-auth/plugins. Migrate OIDC authorization-server integrations to@better-auth/oauth-provider.#10473
ed61b47Thanks @gustavovalverde! - Add transactional OIDC user resolution so applications can link verified issuer and subject pairs to exact existing users while preserving or updating the local profile.#10234
973fddeThanks @gustavovalverde! - The SIWE plugin now issues nonces before the wallet address or Chain ID is known.authClient.siwe.nonce()andauthClient.siwe.getNonce()no longer accept wallet fields,getNoncemust return an ERC-4361 nonce (8-250 alphanumeric characters), and SIWE verification now reads the wallet address and Chain ID from the signed ERC-4361 message.#9864
41cca60Thanks @GautamBytes! - Add auser.validateUserInfoprovisioning gate that lets applications reject an identity before a user is created or a new account is linked. It runs once at the creation step for every method that provisions a user (OAuth, SSO/SAML, email/password, magic link, email OTP, anonymous, SIWE, phone number, admin-created users, and SCIM), including stateless setups with no persistent database.It also re-runs when an existing OAuth or SSO user signs in again (
source.actionis"sign-in"), where it receives the fresh provider email and profile so a domain or org policy can reject a user whose provider identity moved out of bounds. Non-provider returning sign-ins are not re-validated.The callback receives the mapped
userplus asourcedescribing theaction(create-user,link-account, orsign-in), themethod, and provider metadata:source.oauthfor OAuth providers andsource.ssofor OIDC/SAML SSO providers. Return{ error, errorDescription }to reject: browser flows redirect to the error URL and programmatic flows return a403.#10036
ad35eadThanks @bytaesu! - Require Google One Tap server callbacks to resolve a Google client ID before verifying ID tokens. ConfigureoneTap({ clientId })orsocialProviders.google.clientIdwhen using the One Tap plugin.#9057
544f1c6Thanks @gustavovalverde! - feat(two-factor)!: add OTP-only enablement and a discriminated responseenableTwoFactornow accepts amethodparameter ("otp" | "totp", default"totp") and returns a discriminated response with amethodfield.method: "otp"twoFactorEnabled: trueimmediately.{ method: "otp" }.otpOptions.sendOTPto be configured on the server; rejects withOTP_NOT_CONFIGUREDotherwise.method: "totp"(default){ method: "totp", totpURI, backupCodes }.TOTP_NOT_CONFIGUREDiftotpOptions.disableis set.The existing
skipVerificationOnEnableoption remains supported for TOTP enrollment.Breaking changes
enableTwoFactorincludes amethodfield in the response ("otp"or"totp").Patch Changes
#10014
73541c1Thanks @gustavovalverde! - Cloudflare Workers apps can now start when importing Better Auth subpaths such asbetter-auth/db. Beta builds were crashing during module initialization before application code ran.#10299
cf8eaacThanks @momomuchu! - widen drizzle-kit peer dependency range#10501
65fc17cThanks @KingIronMan2011! - Expand the optionaldrizzle-ormpeer range to^0.45.2 || >=1.0.0-rc.1 <2.0.0, matching@better-auth/drizzle-adapterand allowing Drizzle ORM v1 RC installations without peer dependency warnings.#10622
ecd83daThanks @gustavovalverde! - Sign-up no longer deadlocks when session cookie caching uses the JWT strategy on a single-connection SQLite database with native transactions enabled. JWKS key lookups and creation now resolve the transaction-scoped adapter instead of always querying the root connection, so minting a signing key during sign-up joins the surrounding transaction instead of racing it for the only available connection. On multi-connection databases (Postgres, MySQL) this also fixes a silent atomicity gap where a JWKS key created mid-transaction could commit independently of the transaction it was minted in.#10293
fe4c820Thanks @gustavovalverde! -npx auth migratecan now add required columns with static defaults and nullableunique columns to existing SQLite, PostgreSQL, and MySQL tables. Required unique
columns still need distinct values to be backfilled manually before applying the
unique constraint.
#9898
7fe0e2bThanks @ItalyPaleAle! - AddclientAssertionsupport to the Microsoft Entra ID social provider.#9301
03e6c94Thanks @gustavovalverde! - AddallowIdpInitiatedtoGenericOAuthConfigand SSOOIDCConfigto support providers that initiate OAuth without astateparameter (e.g. Clever). When enabled, stateless callbacks restart the OAuth flow server-side with fresh state and PKCE, preserving CSRF protection. Also hardensparseStateagainst undefined request bodies on GET callbacks.#10065
2196ea6Thanks @gustavovalverde! - OAuth and device-authorization responses that carry credentials now consistently sendCache-Control: no-storeandPragma: no-cache, so proxies, CDNs, and browsers never cache them. This covers the token, introspection, and userinfo endpoints, dynamic and admin client registration, client secret rotation, and the device code and device token responses, including the error responses from those endpoints.Endpoints declare this with
metadata: { noStore: true }, and the header set is exported from@better-auth/coreasNO_STORE_HEADERSfor responses built by hand.#10124
06daf70Thanks @gustavovalverde! - Preserve the resolved OAuth user whenoverrideUserInforeturnsnullduring account linking.#9304
e0d2b9eThanks @gustavovalverde! - Propagate sign-out to every connected app and cut off API access immediately, via OIDC Back-Channel Logout 1.0.When a user's session ends at the OP (sign-out,
/oauth2/end-session, admin revoke, ban),@better-auth/oauth-providernow notifies every Relying Party that holds tokens for that session. The user's API access is cut off right away, instead of access tokens staying usable until their own TTL. Each client opts in by registering abackchannel_logout_uri(and optionallybackchannel_logout_session_required) via DCR or the admin client-create endpoint. The provider signs alogout+jwtLogout Token per client and POSTs it to that client in parallel, with a short per-RP timeout.Breaking change. Introspection of an opaque or JWT access token whose bound session has ended now returns
{ active: false }, and/oauth2/userinforejects it withinvalid_token. Previously the token stayed active until its own TTL. If you relied on access tokens outliving the user's session, that no longer holds.Refresh tokens without
offline_accessare revoked on session end;offline_accessrefresh tokens are preserved so long-lived API access can survive the browser session (OIDC Back-Channel Logout 1.0 §2.7). Access-token invalidation on session end is an additional OP hardening choice beyond §2.7, enforced by session liveness, so it holds even when the JWT plugin is disabled.Delivery runs through the host's background task handler when one is configured (Vercel
waitUntil, Cloudflarectx.waitUntil); without a handler it completes inline so notifications are not lost on request teardown. Configureadvanced.backgroundTasks.handleron serverless runtimes to keep sign-out fast.Discovery at
/.well-known/openid-configurationand/.well-known/oauth-authorization-serveradvertisesbackchannel_logout_supported: trueandbackchannel_logout_session_supported: truewhen the JWT plugin is enabled. Every registeredbackchannel_logout_urimust be a credential-free public HTTPS URL without a fragment; loopback HTTP is rejected for both public and confidential clients. CIMD documents cannot register back-channel logout metadata. The SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, also covers aprivate_key_jwtclient'sjwks_uri.Schema changes on
@better-auth/oauth-provider:oauthClient.backchannelLogoutUri: string | nulloauthClient.backchannelLogoutSessionRequired: booleanoauthAccessToken.revoked: Date | nullbetter-auth'ssignJWTgains an optionalheaderargument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such astyp: "logout+jwt", can now set it without reaching for the low-level signing primitives.#10125
a83152eThanks @gustavovalverde! - Create new OAuth accounts in the user creation transaction. Adapters with nativetransaction support roll back the user when the account write fails, while other
adapters still perform the writes sequentially.
#10128
97903c9Thanks @gustavovalverde! - Preserve previously granted OAuth scopes across sign-in re-authentication and refresh-token requests.account.scopenow accumulates monotonically: newly granted scopes are merged in only when added vialinkSocial, and providers returning a narrower scope claim than the user has granted no longer shrink the stored value.#10170
6ddb555Thanks @gustavovalverde! - Bundled dependencies were refreshed to their latest compatible releases, including jose, nanostores, the noble crypto packages, and SimpleWebAuthn. These updates are backward compatible and require no changes to existing projects.#10390
0de88f5Thanks @gustavovalverde! - SCIM connections can now provision Users, Groups, and direct memberships into application-defined provisioning domains without the organization or SSO plugins. Applications can map Group membership to validated custom roles through projections. The service also supports SCIM 2.0 discovery, filtering, pagination, response attribute selection, atomic PATCH operations, and common request patterns used by Microsoft Entra ID and Okta.This replaces the previous SCIM configuration, client APIs, database schema, and organization-backed Group model. Existing SCIM installations cannot migrate provisioning state in place. Follow the SCIM cutover in the 1.7 upgrade guide, including full directory reprovisioning, before resuming traffic.
Deferred database side effects now run only after a successful transaction. A rolled-back User update no longer refreshes its cached profile, and a rolled-back bulk session revocation no longer invalidates sessions.
#10505
d701f90Thanks @gustavovalverde! - One Tap, Electron, and Expo client plugins now compose withcreateAuthClientwithout TypeScript errors, and the resulting client preserves each plugin's inferred actions.#10621
59c4c83Thanks @gustavovalverde! - Allow test instances to enable native database transactions for postgres and mysql.Updated dependencies [
5c45abc,763a267,5d38b13,692b22c,ea06c5a,3d04fab,430c895,de8394d,dbd302e,8784c1c,ecd83da,e4818b5,7fe0e2b,0683a5f,e7eb45b,7c7313c,aedcb97,03e6c94,4f53b61,2196ea6,91f235f,34558bc,1e5b808,93d3871,97903c9,ed61b47,0de88f5,3a79aff,41cca60,d701f90]:v1.6.34Compare Source
better-authBug Fixes
ACCOUNT_NOT_FOUNDwhen retrieving an access token after stateless OAuth sign-in links a new provider to an existing user (#11300)For detailed changes, see
CHANGELOG@better-auth/ssoBug Fixes
samlConfig.callbackUrlas their ACS URL withoutspMetadata.metadata, which had failed withinvalid_saml_responsesince 1.6.21 (#11730)For detailed changes, see
CHANGELOGContributors
Thanks to everyone who contributed to this release:
@bytaesu, @gustavovalverde
Full changelog:
v1.6.33...v1.6.34v1.6.33Compare Source
@better-auth/coreBug Fixes
For detailed changes, see
CHANGELOGContributors
Thanks to everyone who contributed to this release:
@better-release[bot]
Full changelog:
v1.6.32...v1.6.33v1.6.32Compare Source
better-authBug Fixes
For detailed changes, see
CHANGELOGContributors
Thanks to everyone who contributed to this release:
@better-release[bot]
Full changelog:
v1.6.31...v1.6.32v1.6.31Compare Source
better-authFeatures
For detailed changes, see
CHANGELOG@better-auth/coreFeatures
For detailed changes, see
CHANGELOGContributors
Thanks to everyone who contributed to this release:
@better-release[bot]
Full changelog:
v1.6.30...v1.6.31v1.6.30Compare Source
Patch Changes
07c1718]:v1.6.29Compare Source
Patch Changes
#10805
e6e1b4eThanks @Emmaccen! - Speed up batch session revocation when secondary storage is enabled by deleting sessions in parallel.Updated dependencies []:
v1.6.28Compare Source
Patch Changes
#10769
773de54Thanks @bytaesu! - Prevent duplicate session requests during transient remounts while ensuring incomplete refreshes are revalidated.#10794
2ad2928Thanks @bytaesu! - Restore client plugin declaration compatibility for downstream TypeScript consumers.Updated dependencies []:
v1.6.27Compare Source
Patch Changes
#10657
2ae491eThanks @bytaesu! - Aligned endpoint and middleware context types with runtime route parameters, and preserved response headers when resolving sessions from endpoint contexts.#10676
90b5093Thanks @bytaesu! - Deduplicate in-flight session requests when React retries a suspended component.Updated dependencies [
2ae491e]:v1.6.26Compare Source
Patch Changes
#10619
9ede805Thanks @jeroenvandermerwe! - Ensure database rate-limit cleanup completes when no background task handler is configured.#10608
5a811f1Thanks @bytaesu! - Pass the email verification type to custom OTP generators after email sign-up.#10605
d8327f1Thanks @XXMOHAMED012! - The email OTP verification check no longer reveals whether an email is registered before the OTP itself is verified.#10513
e2c73fbThanks @mrosberghaus! - FixjwtClient()collapsingcreateAuthClienttype inference when combined with other client plugins such asinferAdditionalFields. Additional user fields (for example onupdateUser) are preserved again.#10635
af50c45Thanks @krish-vachhani! - FixoneTapClient()collapsingcreateAuthClienttype inference when combined with other client plugins. TheoneTapaction is available on the client again.#10633
701cd43Thanks @gustavovalverde! - 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 of it.#10599
e7b0ebaThanks @bytaesu! - Preserve Apple user data fromform_postcallbacks when usingoAuthProxy.#10552
2b4a14fThanks @bytaesu! - Allow users to retry email OTP password resets after entering an invalid password.#10467
7552a3bThanks @jlucaso1! - ImprovenextCookiesperformance in instrumented Next.js applications.#10580
ea38fcaThanks @Emmaccen! - Skip invalid secondary-storage session entries without discarding other valid sessions.#10520
a03e4c1Thanks @bytaesu! - Ensure deleting a user also removes their sessions from secondary storage.Updated dependencies [
a30e274]:v1.6.25Compare Source
Patch Changes
#10479
5124c34Thanks @krish-vachhani! - Prevent Google One Tap from creating new users when sign-up is disabled for the Google provider.#10444
7439359Thanks @birkskyum! - Expose the real$fetchinstance and$storeatoms from the Solid client instead of resolving them as dynamic API routes.Updated dependencies [
0ffd1fb]:v1.6.24Compare Source
Patch Changes
#10235
03dc5a0Thanks @ping-maxwell! - Fixes silent foreign-key and adapter-join misrouting when a user remaps a built-in model name to a string that collides with another schema key#10357
7508940Thanks @c-nicol! - Fixes Kysely migration generation for new-table fields that are both unique: true and index: true.#10342
bae7198Thanks @ping-maxwell! - Fixorganization.listMembersfailing with "User not found for member" for orgs with more than ~100 members by applying the same membership limit to the users query.#10336
ef4d273Thanks @Tushar-Khandelwal-2004! - Prevent verification callbacks from failing auth requests when cloning the request throws.#10333
99dbdd7Thanks @c-nicol! - Fixes Drizzle schema generation for fields that are both unique: true and index: true.#10368
086ca91Thanks @gaurav0107! - Force-validate the requestOriginon the magic-link (/sign-in/magic-link) and email-otp (/email-otp/send-verification-otp) send endpoints, including cookieless requests, to match the built-in/sign-in/emailand/sign-up/emailroutes. A cookieless cross-origin POST can no longer trigger a magic-link or verification-OTP email to an arbitrary address. Cookieless requests that carry noOrigin(server-to-server) are unaffected.#10290
8f2deddThanks @GautamBytes! - Expose the remote MCP auth client's 401 challenge headers to browser clients using CORS.#10453
4e685eeThanks @ping-maxwell! - OpenAPI now includesuser.additionalFieldsand plugin user schema fields (e.g. username pluginusername/displayUsername) on/sign-up/emailand/update-userrequest bodies.#10190
3bf0e49Thanks @gaurav-init! - Pass the endpoint context as the second argument tobeforeDeleteOrganizationandafterDeleteOrganizationhooks in the organization plugin, matching the signature shown in the docs and the existingdatabaseHookspattern. The Stripe plugin'sbeforeDeleteOrganizationwrapper now forwards the context to user-supplied hooks instead of dropping it.#10040
f59a0eeThanks @shiminshen! - Organization invitations now let the database generate theiridwhen ID generation is delegated to the database (e.g.advanced.database.generateId: "uuid"with a UUID-capable adapter such as Postgres), matching every other model. PreviouslycreateInvitationalways generated the invitationidin application code, so invitation rows received an app-generated value instead of a database-generated one while organizations, members and teams correctly deferred to the database (better-auth/better-auth#10024). A caller-provided id (e.g. viabeforeCreateInvitation) is still honored.#10302
0f2cc1bThanks @momomuchu! - Prefer exact schema-key matches overmodelNamealiases ingetDefaultModelName, so remapping a built-in table onto another table's schema key (e.g.user.modelName = "account") does not reroute internal adapter queries to the wrong table.#9787
ae78109Thanks @ping-maxwell! - Fixes an issue whereuseSession({ throw: true })incorrectly excludednullfrom itsdatatype.#10222
46d2bf0Thanks @ping-maxwell! - fix: add no-store cache-control headers to get-session route#10316
29a373eThanks @vinay-oppuri! - Recognize SQLiteBIGINTas a valid number type in migration diffs so database-backed rate limiter columns likelastRequestno longer report spurious pending changes on every run.#10379
f6d18faThanks @ping-maxwell! - fix(client): restore auth query revalidation and signal listeners after remount#5753
f23ce50Thanks @ping-maxwell! - feat(last-login-method): beforeStoreCookie option for GDPR compliance#10376
c4d1ddaThanks @ping-maxwell! - Pass the request endpoint context as a third argument toverifyIdToken, so custom ID token verifiers can read request headers (for example Apple'suser-agentrequirement).Updated dependencies [
6758231,54fab08,c4d1dda]:Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate CLI.
b518d06c33toa474bd363fUpdate dependency better-auth to v1.6.24to Update dependency better-auth to v1.6.25a474bd363ftof27f3b2939Update dependency better-auth to v1.6.25to Update dependency better-auth to v1.6.26f27f3b2939to0f41e11b29Update dependency better-auth to v1.6.26to Update dependency better-auth to v1.6.270f41e11b29todefe51e91eUpdate dependency better-auth to v1.6.27to Update dependency better-auth to v1.6.28defe51e91eto6d01c96957Update dependency better-auth to v1.6.28to Update dependency better-auth to v1.6.296d01c96957to32b35f475eUpdate dependency better-auth to v1.6.29to Update dependency better-auth to v1.7.032b35f475eto19b2db286eUpdate dependency better-auth to v1.7.0to Update dependency better-auth to v1.7.119b2db286eto98757bc7e5Update dependency better-auth to v1.7.1to Update dependency better-auth to v1.7.298757bc7e5tocff14a304eUpdate dependency better-auth to v1.7.2to Update dependency better-auth to v1.7.3cff14a304etoa9ea98f45aUpdate dependency better-auth to v1.7.3to Update dependency better-auth to v1.7.4a9ea98f45atod949416bc5Update dependency better-auth to v1.7.4to Update dependency better-auth to v1.7.5d949416bc5to345c0c42a1Update dependency better-auth to v1.7.5to Update dependency better-auth to v1.7.6345c0c42a1tocfc85625c1Update dependency better-auth to v1.7.6to Update dependency better-auth to v1.7.7cfc85625c1to2ef64116ebUpdate dependency better-auth to v1.7.7to Update dependency better-auth to v1.7.92ef64116ebto9d0e084539Update dependency better-auth to v1.7.9to Update dependency better-auth to v1.7.109d0e084539to8331b4c827Update dependency better-auth to v1.7.10to Update dependency better-auth to v1.7.118331b4c827toc144dfc8acUpdate dependency better-auth to v1.7.11to Update dependency better-auth to v1.7.12c144dfc8acto30793376a8Update dependency better-auth to v1.7.12to Update dependency better-auth to v1.7.1330793376a8to4f16dacba3Update dependency better-auth to v1.7.13to Update dependency better-auth to v1.7.144f16dacba3toe984683d74Update dependency better-auth to v1.7.14to Update dependency better-auth to v1.7.15e984683d74to6523f8dc32Update dependency better-auth to v1.7.15to Update dependency better-auth to v1.7.166523f8dc32to5e3203eec3Update dependency better-auth to v1.7.16to Update dependency better-auth to v1.7.175e3203eec3to8fd0d4f851Update dependency better-auth to v1.7.17to Update dependency better-auth to v1.7.188fd0d4f851toe03870a416Update dependency better-auth to v1.7.18to Update dependency better-auth to v1.7.19e03870a416to6fdf85c3b6Update dependency better-auth to v1.7.19to Update dependency better-auth to v1.7.206fdf85c3b6to2428dd637cUpdate dependency better-auth to v1.7.20to Update dependency better-auth to v1.7.212428dd637cto2f8e861c4bUpdate dependency better-auth to v1.7.21to Update dependency better-auth to v1.7.222f8e861c4bto069a2bba12Update dependency better-auth to v1.7.22to Update dependency better-auth to v1.7.23069a2bba12to43a2f9eb1cUpdate dependency better-auth to v1.7.23to Update dependency better-auth to v1.7.25View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.