Sourced from better-auth's releases.
v1.7.0
Blog post: Better Auth 1.7
better-auth❗ Breaking Changes
- Moved database joins out of
experimentalinto the stableadvanced.database.joinsoption (#10359)Migration: Replace
experimental: { joins: true }withadvanced: { database: { joins: true } }. Drizzle and Prisma users should regenerate their schema (npx auth@latest generate) so it includes the required relations.- Scoped account identity by trusted issuer, keying accounts on
(issuer, accountId)(#10403)Migration: Accounts now require
Account.issuer. Read provider identity fromaccountInfo.account.accountId, dropmapping.idfrom SSO configs, and give themicrosoftEntraIdhelper a concrete tenant GUID. Apply the account-identity backfill in the 1.7 upgrade guide before deploying.- Required captcha endpoint entries to match full auth paths, with wildcard support (#10004)
Migration: Replace partial paths such as
/sign-inwith explicit wildcards like/sign-in/*or/sign-in/**.- Moved the MCP plugin into its own
@better-auth/mcppackage built on the OAuth provider (#9992)Migration: Install
@better-auth/mcpand@better-auth/cimd, add the now-requiredjwt()plugin, and move options nested underoidcConfigto flatmcp({ ... })options. RenamewithMcpAuthtorequireMcpAuthandmcpHandlertocreateMcpProtectedRequestHandler. Regenerate the schema (npx auth migrate):oauthApplicationbecomesoauthClient, plus newoauthRefreshTokenandoauthClientAssertiontables.- Added OIDC back-channel logout so ending a session cuts off every connected app's API access (#9304)
Migration: Introspecting an access token whose session has ended now returns
{ active: false }, and/oauth2/userinforejects it. Clients opt into notifications by registeringbackchannel_logout_uri. Run the schema migration for the newoauthClientandoauthAccessTokencolumns.- Modeled OAuth protected resources explicitly, with per-resource TTLs, scopes, claims, and signing pins (#9648)
Migration:
validAudiencesis removed: move each resource identifier intoresourcesand link restricted clients throughoauthClientResource.@better-auth/mcpnow requires an explicitresource. Runnpx @better-auth/cli generateand apply the migration before deploying.- Decoupled SCIM provisioning from the organization plugin (#10390)
Migration: SCIM configuration, client APIs, database schema, and the Group model are all replaced, and provisioning state cannot migrate in place. Follow the SCIM cutover in the 1.7 upgrade guide, including a full directory reprovision, before resuming traffic.
- Added OTP-only two-factor enablement with a discriminated
enableTwoFactorresponse (#9057)Migration:
enableTwoFactornow returns amethodfield ("otp"or"totp"); narrow on it before readingtotpURIandbackupCodes. Passmethod: "otp"for OTP enrollment, which requiresotpOptions.sendOTP.- Resolved the auth origin from
Hostby default when using a dynamicbaseURL(#9134)Migration: If your proxy exposes the public hostname only through
x-forwarded-host, setadvanced.trustedProxyHeaders: true. Deployments where the proxy rewritesHost(nginx default, Vercel, Cloudflare, Netlify) are unaffected.- Added unique lookup indexes for the device authorization
deviceCodeanduserCodecolumns (#10059)Migration: Resolve duplicate code 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.
- Enforced S256 PKCE in the Electron sign-in flow and hardened custom-scheme origin checks (#9645)
Migration: Upgrade the
@better-auth/electronclient and server together and add your app's scheme totrustedOrigins. Thecode_challenge_methodparameter anddisableOriginOverrideoption are removed, and host-bearing custom-scheme entries now match that host exactly.- Identified Microsoft Entra accounts by the stable
oidclaim (#10204)Migration: Migrate existing Microsoft account rows created from
subbefore upgrading. Tokens without a validoidare rejected.- Required a Google client ID before Google One Tap verifies ID tokens (#10036)
Migration: Configure
oneTap({ clientId })orsocialProviders.google.clientId.- Removed the deprecated
oidcProviderplugin (#10031)Migration: Move OIDC authorization-server integrations to
@better-auth/oauth-provider.- Rewrote the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults (#9069)
Migration: Replace
signIn.oauth2({ providerId })withsignIn.social({ provider }),oauth2.link()withlinkSocial(), and dropgenericOAuthClient(). Callbacks move to/api/auth/callback/:id,pkcenow defaults totrue, andissuerandrequireIssuerValidationare removed in favor of OIDC discovery.- Separated OAuth device grant ownership into
oauthDeviceAuthorization()(#10746)Migration: The OAuth integration replaces the optional
resourcecolumn withoauthClientIdandresources, so regenerate and apply the schema. Let pending device codes expire before upgrading from an earlier 1.7 prerelease.- Verified provider
id_tokenswith a single shared verifier (#9828)Migration: Custom
UpstreamProviderimplementations replace the removedverifyIdTokenmethod with anidTokenconfig carrying a JWKS source, issuer, and audience. PayPal clientid_tokensign-in now returnsID_TOKEN_NOT_SUPPORTED; its redirect flow is unchanged.Features
- Added
clientAssertionsupport to the Microsoft Entra ID social provider (#9898)- Made the
Authinstance directly fetchable (#9431)- Added per-provider
requireEmailVerificationfor social sign-in (#9929)- Added a
user.validateUserInfogate for rejecting an identity before a user is created or linked (#9864)- Added
hydrateSessionsouseSessionreturns server-fetched data on the first render (#8733)- Added compound table indexes to plugin database schemas (#10402)
... (truncated)
Sourced from better-auth's changelog.
1.7.0
Minor 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 by settingdisplayUsername: 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()- Callback URL changed from
/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>
... (truncated)
ccd57c2
docs(changelog): align v1.7 release notes with final behavior (#10846)f577ec5
chore: exit pre-release mode for v1.7.069258d1
chore: sync main to nexte84ec5e
chore: release v1.6.30 (#10840)bc93b27
chore: release v1.7.0-rc.6 (#10772)58c49eb
chore: release v1.6.29 (#10809)e6e1b4e
perf(db): replace sequential get-then-delete loop with parallel deletes
in de...80799e6
chore: sync main to next3e485bf
docs(username): fix displayUsername release notes (#10776)65fc17c
fix(deps): align drizzle-orm peer range with
drizzle-adapter (#10501)