Better Auth Release Notes
111 release notes curated from 1 source by the Releasebot Team. Last updated: Jul 3, 2026
Better Auth Products
- Jul 2, 2026
- Date parsed from source:Jul 2, 2026
- First seen by Releasebot:Jul 3, 2026
v1.7.0-rc.1
Better Auth adds Yandex as a supported OAuth social provider and improves database migration reliability and adapter behavior. The release also fixes affected row counting in D1 and postgres-js, plus better escaping for string default values in generated Drizzle schema.
better-auth
Features
- Added Yandex as a supported OAuth social provider (#9138)
Bug Fixes
- Fixed auth migrate to no longer abort when adding required or unique columns to an existing table (#10293)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
- Fixed affected row counting for D1 and postgres-js adapters (#10257)
For detailed changes, see CHANGELOG
auth
Bug Fixes
- Fixed string default values to be properly escaped in generated Drizzle schema (#10259)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@bytaesu, @gustavovalverde, @vladflotsky
Full changelog: v1.7.0-rc.0...v1.7.0-rc.1
Original source - Jun 29, 2026
- Date parsed from source:Jun 29, 2026
- First seen by Releasebot:Jun 30, 2026
v1.6.23
Better Auth adds Yandex as a social OAuth provider and improves reliability across adapters and billing flows. This release also fixes affected row counting, organization subscription actions, and Drizzle schema escaping for smoother auth and billing behavior.
better-auth
Features
Added Yandex as a social OAuth provider (#9138)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
Fixed affected row counting for D1 and postgres-js adapters (#10257)
For detailed changes, see CHANGELOG
@better-auth/stripe
Bug Fixes
Fixed organization subscription actions (cancel, upgrade, restore, and the billing portal) that could act on the wrong organization.
For detailed changes, see CHANGELOG
auth
Bug Fixes
Fixed string default values not being properly escaped in the generated Drizzle schema (#10259)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@bytaesu, @vladflotsky
Full changelog: v1.6.22...v1.6.23
Original source All of your release notes in one feed
Join Releasebot and get updates from Better Auth and hundreds of other software products.
- Jun 26, 2026
- Date parsed from source:Jun 26, 2026
- First seen by Releasebot:Jun 27, 2026
v1.7.0-rc.0
Better Auth ships a major release with OAuth provider hardening, MCP split into its own package, back-channel logout, protected resources, DPoP, and stronger SSO and Electron security. It also adds two-factor, SCIM, i18n, and broader RFC-compliant auth updates.
better-auth
❗ Breaking Changes
- feat(captcha)!: support wildcard endpoint matching (#10004)
- feat(mcp)!: ship MCP as its own package built on the OAuth provider (#9992)
The route helper is renamed requireMcpAuth (was withMcpAuth), and the remote client is createMcpResourceClient (was createMcpAuthClient). requireMcpAuth verifies the bearer token against the published JWKS and passes the verified JWT claims to your handler.
To migrate, install @better-auth/mcp, add the jwt() plugin (now required for token signing), and move options that were nested under oidcConfig to flat options on mcp({ ... }). The database models change: oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables. Regenerate or migrate your schema with npx auth migrate or npx auth generate.
- feat(oauth-provider)!: add OIDC back-channel logout (#9304)
When a user's session ends at the OP (sign-out, /oauth2/end-session, admin revoke, ban), @better-auth/oauth-provider now 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 a backchannel_logout_uri (and optionally backchannel_logout_session_required) via DCR or the admin client-create endpoint. The provider signs a logout+jwt Logout 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/userinfo rejects it with invalid_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_access are revoked on session end; offline_access refresh 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, Cloudflare ctx.waitUntil); without a handler it completes inline so notifications are not lost on request teardown. Configure advanced.backgroundTasks.handler on serverless runtimes to keep sign-out fast.
Discovery at /.well-known/openid-configuration and /.well-known/oauth-authorization-server advertises backchannel_logout_supported: true and backchannel_logout_session_supported: true when the JWT plugin is enabled. Registering a backchannel_logout_uri rejects fragments, non-http(s) schemes, and non-HTTPS targets on confidential clients. Its SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, now also covers a private_key_jwt client's jwks_uri.
Schema changes on @better-auth/oauth-provider:
oauthClient.backchannelLogoutUri: string | null
oauthClient.backchannelLogoutSessionRequired: boolean
oauthAccessToken.revoked: Date | nullbetter-auth's signJWT gains an optional header argument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such as typ: "logout+jwt", can now set it without reaching for the low-level signing primitives.
- feat(oauth-provider)!: model OAuth protected resources explicitly (#9648)
validAudiences is removed. Move each existing resource identifier into resources; link clients that should be limited to specific resources through oauthClientResource or Dynamic Client Registration resources.
Access-token issuance now applies resource policy to the requested RFC 8707 resource values. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emits jti, and keeps repeated resource form parameters.
Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource refreshTokenTtl longer than refreshTokenExpiresIn will see refresh tokens expire at the provider default instead of the longer resource value.
JWT signing can now honor per-resource pins. signJWT() accepts signingKeyId and signingAlgorithm; JWKS adapters expose getKeyById() and getLatestKeyByAlg(). The jwks table adds nullable alg and crv columns, and keyPairConfigs can provision multiple algorithms in one keyring.
After upgrading, run npx @better-auth/cli generate and apply the migration before deploying. The migration adds oauthResource, oauthClientResource, and the new jwks columns. Without it, resources using signingAlgorithm cannot 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/mcp now requires an explicit resource option. 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. Existing mcp({ loginPage, consentPage }) setups should add a protected MCP resource identifier, for example resource: "https://api.example.com/mcp".
- feat(two-factor)!: add OTP enablement and discriminated response (#9057)
enableTwoFactor now accepts a method parameter ("otp" | "totp", default "totp") and returns a discriminated response with a method field.
method: "otp"
Sets twoFactorEnabled: true immediately.
Returns { method: "otp" }.
Requires otpOptions.sendOTP to be configured on the server; rejects with OTP_NOT_CONFIGURED otherwise.method: "totp" (default)
Returns { method: "totp", totpURI, backupCodes }.
Rejects with TOTP_NOT_CONFIGURED if totpOptions.disable is set.Breaking changes
Removed skipVerificationOnEnable: use method: "otp" for immediate activation, or the standard TOTP verification flow.
Response shape changed: enableTwoFactor includes a method field in the response ("otp" or "totp").
fix(auth)!: ignore x-forwarded headers by default on dynamic baseURL (#9134)
Requests using baseURL: { allowedHosts } now resolve the auth origin from Host by 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, set advanced.trustedProxyHeaders: true. Deployments where the proxy rewrites Host to the public hostname (nginx default, Vercel, Cloudflare, and Netlify) are unaffected.
Migration:
betterAuth({ baseURL: { allowedHosts: [...] }, advanced: { trustedProxyHeaders: true, }, });- fix(electron)!: enforce S256 PKCE and harden origin checks (#9645)
The Electron sign-in flow now mandates PKCE S256. Plain PKCE is rejected: the code_challenge_method parameter is gone and every authorization code is verified by hashing the verifier with SHA-256. The server no longer trusts an electron-origin header to set the request Origin. The Electron client now sends a real Origin (for example myapp:/), so upgrade the @better-auth/electron client and server together and make sure your app's scheme is in trustedOrigins. The unused disableOriginOverride option is removed.
Custom-scheme entries in trustedOrigins now match by scheme and authority instead of string prefix. A host-less entry such as myapp:// or exp:// still trusts every host of that scheme, but a host-bearing entry such as myapp://callback matches that host exactly, so it is no longer satisfied by myapp://callback.attacker.tld.
- fix(one-tap)!: require client id for audience validation (#10036)
- refactor!: remove deprecated oidc-provider plugin (#10031)
- refactor(generic-oauth)!: rewrite as first-class social provider with RFC compliance (#9069)
Breaking changes:
signIn.oauth2({ providerId }) replaced by signIn.social({ provider })
oauth2.link() replaced by linkSocial()
Callback URL changed from /api/auth/oauth2/callback/:id to /api/auth/callback/:id
genericOAuthClient() removed; generic OAuth providers now use the standard social client APIs
pkce defaults to true (was false); set pkce: false for providers that reject PKCE
authorizationUrlParams and tokenUrlParams only accept Record<string, string>
issuer and requireIssuerValidation config fields removed; issuer validation is automatic via OIDC discovery
mapProfileToUser profile typed as OAuth2UserInfo & Record<string, unknown>
refactor(oauth)!: verify provider id_tokens with a single shared verifier (#9828)
Client-submitted id_token sign-in (signIn.social({ idToken }) and account linking) is verified by one function instead of a per-provider verifyIdToken method. Each provider declares an idToken config 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 idToken config, and the client id_token path returns ID_TOKEN_NOT_SUPPORTED. PayPal sign-in through the redirect flow is unchanged.
Custom providers that implement UpstreamProvider directly replace the removed verifyIdToken method with an idToken config:
idToken: { jwks: createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")), issuer: "https://issuer.example", audience: clientId, },For verification that cannot use a local JWKS, pass idToken: { verify: async (token, nonce) => boolean }. The verifyIdToken and disableIdTokenSignIn provider options are unchanged.
Features
- feat: add clientAssertion support to the Microsoft Entra ID social provider (#9898)
- feat: make Auth instance fetchable (#9431)
- feat(auth): add per-provider requireEmailVerification for social sign-in (#9929)
- feat(auth): add user.validateUserInfo provisioning gate (#9864)
- feat(client): add hydrateSession for SSR session hydration (#8733)
- feat(generic-oauth,sso): support IDP-initiated flows via secure bounce (#9301)
- feat(generic-oauth): forward refreshTokenParams to token endpoint (#9948)
- feat(generic-oauth): verify discovery id_tokens and enable id_token sign-in (#9966)
- feat(oauth-provider): add DPoP support (#10039)
- feat(oauth-provider): compute at_hash in id tokens per OIDC Core §3.1.3.6 (#9079)
- feat(oauth): add private_key_jwt client authentication (RFC 7523) (#8836)
- feat(oauth): enforce no-store on credential responses via a declarative flag (#10065)
- feat(oauth): per-request additionalParams and loginHint (#9305)
- feat(oauth): server-trusted state channel; fix anonymous cookieless linking (#9930)
- feat(org): allow passing userId and organizationId to listUserTeams API (#8977)
- feat(phone-number): add server-side OTP consumption API (#9766)
- feat(session): support JWKS-backed JWT session cookie cache (#8931)
- feat(username): add immutable username option (#9240)
Bug Fixes
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.
- fix(generic-oauth): bind id token nonce in redirect flow (#10095)
- fix(oauth): create new oauth account in transaction (#10125)
- fix(oauth): derive redirect URI from per-request baseURL (#10127)
- fix(oauth): preserve account.scope across re-auth and refresh (#10128)
- fix(oauth): preserve user on null profile override (#10124)
- fix(session): fire session-delete hooks for preserved sessions on secondaryStorage (#9969)
- refactor(oauth): single-source Basic credentials + getHttpTestInstance (#9657)
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
❗ Breaking Changes
- feat(mcp)!: ship MCP as its own package built on the OAuth provider (#9992)
The route helper is renamed requireMcpAuth (was withMcpAuth), and the remote client is createMcpResourceClient (was createMcpAuthClient). requireMcpAuth verifies the bearer token against the published JWKS and passes the verified JWT claims to your handler.
To migrate, install @better-auth/mcp, add the jwt() plugin (now required for token signing), and move options that were nested under oidcConfig to flat options on mcp({ ... }). The database models change: oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables. Regenerate or migrate your schema with npx auth migrate or npx auth generate.
- feat(oauth-provider)!: add OIDC back-channel logout (#9304)
When a user's session ends at the OP (sign-out, /oauth2/end-session, admin revoke, ban), @better-auth/oauth-provider now 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 a backchannel_logout_uri (and optionally backchannel_logout_session_required) via DCR or the admin client-create endpoint. The provider signs a logout+jwt Logout 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/userinfo rejects it with invalid_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_access are revoked on session end; offline_access refresh 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, Cloudflare ctx.waitUntil); without a handler it completes inline so notifications are not lost on request teardown. Configure advanced.backgroundTasks.handler on serverless runtimes to keep sign-out fast.
Discovery at /.well-known/openid-configuration and /.well-known/oauth-authorization-server advertises backchannel_logout_supported: true and backchannel_logout_session_supported: true when the JWT plugin is enabled. Registering a backchannel_logout_uri rejects fragments, non-http(s) schemes, and non-HTTPS targets on confidential clients. Its SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, now also covers a private_key_jwt client's jwks_uri.
Schema changes on @better-auth/oauth-provider:
oauthClient.backchannelLogoutUri: string | null
oauthClient.backchannelLogoutSessionRequired: boolean
oauthAccessToken.revoked: Date | nullbetter-auth's signJWT gains an optional header argument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such as typ: "logout+jwt", can now set it without reaching for the low-level signing primitives.
- feat(oauth-provider)!: enforce max_age (#9936)
- feat(oauth-provider)!: make id-token claim authority explicit (#10140)
customIdTokenClaims, extension ID-token claims, and per-issuance idTokenClaims can no longer set OIDC/JWT protocol claims such as issuer, subject, audience, token lifetime, nonce, session or hash binding, auth_time, acr, amr, or azp. Namespaced custom claims still appear in ID tokens.
- feat(oauth-provider)!: model OAuth protected resources explicitly (#9648)
validAudiences is removed. Move each existing resource identifier into resources; link clients that should be limited to specific resources through oauthClientResource or Dynamic Client Registration resources.
Access-token issuance now applies resource policy to the requested RFC 8707 resource values. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emits jti, and keeps repeated resource form parameters.
Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource refreshTokenTtl longer than refreshTokenExpiresIn will see refresh tokens expire at the provider default instead of the longer resource value.
JWT signing can now honor per-resource pins. signJWT() accepts signingKeyId and signingAlgorithm; JWKS adapters expose getKeyById() and getLatestKeyByAlg(). The jwks table adds nullable alg and crv columns, and keyPairConfigs can provision multiple algorithms in one keyring.
After upgrading, run npx @better-auth/cli generate and apply the migration before deploying. The migration adds oauthResource, oauthClientResource, and the new jwks columns. Without it, resources using signingAlgorithm cannot 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/mcp now requires an explicit resource option. 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. Existing mcp({ loginPage, consentPage }) setups should add a protected MCP resource identifier, for example resource: "https://api.example.com/mcp".
- fix(oauth-provider)!: bind client authentication to the issuing grant (#10063)
- fix(oauth-provider)!: bind RFC 8707 resource indicators to the authorization grant (#9836)
Breaking change: when the authorization includes a resource, the token and refresh requests may only narrow it. A request for a resource the authorization did not cover returns invalid_target. The customAccessTokenClaims callback now receives a resources array in place of the resource string.
Migration: run the schema migration (npx @better-auth/cli migrate, or generate if you manage the schema yourself) to add the new resource columns.
- fix(oauth-provider)!: return RFC-compliant error envelopes from validation failures (#9277)
An internal createOAuthEndpoint wrapper now translates zod validation failures into the envelope required by RFC 6749 §5.2, 7009 §2.2.1, 7662 §2.3, and 7591 §3.2.2. Failing issues are routed per field:
an absent required value maps to errorCodesByField[name].missing or the endpoint's defaultError.
an unsupported value (unknown enum member) maps to errorCodesByField[name].invalid or defaultError.
any other failure (wrong type, duplicated query params, invalid format, refinement) maps to defaultError, so RFC 6749 §3.1 malformed requests emit the endpoint's default code regardless of field.All six OAuth endpoints (/oauth2/token, /oauth2/authorize, /oauth2/revoke, /oauth2/introspect, /oauth2/register, /oauth2/end-session) now return RFC-compliant errors for malformed requests. /oauth2/authorize validation failures redirect to the relying party with error, error_description, echoed state, and iss whenever client_id and redirect_uri resolve against the registered client; requests without a trusted RP fall back to the server error page.
Additional RFC compliance fixes on the same endpoints:
/oauth2/revoke and /oauth2/introspect now ignore an unknown token_type_hint instead of rejecting it. RFC 7009 §2.2.1 and RFC 7662 §2.1 reserve unsupported_token_type for the token itself, not the hint value; servers MAY ignore unrecognized hints and search across supported token types.
/oauth2/authorize error redirects now respect OIDC Core 1.0 §5 response modes. Errors for response_type=token or id_token are delivered in the URL fragment per RFC 6749 §4.2.2.1; an explicit response_mode=query overrides the default.- refactor(generic-oauth)!: rewrite as first-class social provider with RFC compliance (#9069)
Breaking changes:
- signIn.oauth2({ providerId }) replaced by signIn.social({ provider })
- oauth2.link() replaced by linkSocial()
- Callback URL changed from /api/auth/oauth2/callback/:id to /api/auth/callback/:id
- genericOAuthClient() removed; generic OAuth providers now use the standard social client APIs
- pkce defaults to true (was false); set pkce: false for providers that reject PKCE
- authorizationUrlParams and tokenUrlParams only accept Record<string, string>
- issuer and requireIssuerValidation config fields removed; issuer validation is automatic via OIDC discovery
- mapProfileToUser profile typed as OAuth2UserInfo & Record<string, unknown>
Features
- feat: add token endpoint client authentication (#9625)
- feat(cimd): add Client ID Metadata Document plugin (#9159)
- feat(oauth-provider): add DPoP support (#10039)
- feat(oauth-provider): add extension surface (#10030)
- feat(oauth-provider): add refresh token reuse interval (#10145)
- feat(oauth-provider): allow confidential DCR clients without PKCE (#10146)
- feat(oauth-provider): compute at_hash in id tokens per OIDC Core §3.1.3.6 (#9079)
- feat(oauth-provider): consistent and audience-scoped token introspection (#10045)
- feat(oauth-provider): expose sessionId to id_token claim contributors (#10113)
- feat(oauth-provider): honor requested UserInfo claims via a claim registry (#10156)
- feat(oauth-provider): support protected dynamic client registration (#10037)
- feat(oauth): add private_key_jwt client authentication (RFC 7523) (#8836)
- feat(oauth): enforce no-store on credential responses via a declarative flag (#10065)
- feat(oauth): server-trusted state channel; fix anonymous cookieless linking (#9930)
Bug Fixes
- fix(oauth-provider): accept UserInfo form-body tokens (#10155)
- fix(oauth-provider): allow nonce-bound offline access without PKCE (#10153)
- fix(oauth-provider): challenge invalid userinfo tokens (#10068)
- fix(oauth-provider): handle OIDC authorization request inputs (#10151)
- fix(oauth-provider): keep OIDC scope claims on UserInfo (#10152)
- fix(oauth-provider): make private_key_jwt jti single-use atomic across processes (#9964)
- fix(oauth-provider): make redirect_uri conditional at the token endpoint (#10159)
- fix(oauth-provider): preserve dcr client key metadata (#10144)
- fix(oauth-provider): redirect missing response_type errors (#10149)
- fix(oauth-provider): reject authorization code replay correctly (#10150)
- fix(oauth-provider): report unsupported_token_type for JWT access-token revocation (#9970)
- fix(oauth-provider): return invalid_grant for cross-client refresh tokens (#10154)
- refactor(oauth): single-source Basic credentials + getHttpTestInstance (#9657)
For detailed changes, see CHANGELOG
@better-auth/sso
❗ Breaking Changes
- feat(sso)!: support multiple IdP signing certificates (#8805)
SAML signing certificates now accept an array of PEM strings, so administrators can publish a new IdP cert alongside the old one and complete the rotation without forcing every active session to re-authenticate. Responses signed by any listed cert are accepted.
samlConfig: { idpMetadata: { cert: [currentPem, nextPem], }, }Both samlConfig.cert and samlConfig.idpMetadata.cert accept either a single PEM string or an array. When both are set, idpMetadata.cert wins.
Breaking: response shape
The management endpoints (getSSOProvider, listSSOProviders, updateSSOProvider) now return samlConfig.certificate as an array of parsed certificates in every case, even when a single cert is configured. The field is absent only when certs live inside idpMetadata.metadata. Update consumers to read an array; no more Array.isArray branching.
Validation
Registration now rejects SAML configs that supply no signing-cert source. samlify needs either an idpMetadata.metadata XML document (which embeds the certs) or an explicit PEM under cert or idpMetadata.cert. Configs missing both fail with CERT_SOURCE_MISSING.
Fix
SAML Single Logout could fail to decrypt encrypted LogoutResponse payloads because the IdP entity was constructed without privateKey, encPrivateKey, or encPrivateKeyPass on that code path. All three are now applied on every IdP construction.
- fix(auth)!: harden validateUserInfo source contract (#9940)
- fix(sso)!: harden SAML response validation (InResponseTo, Audience, SessionIndex) (#9055)
Breaking Changes
- allowIdpInitiated now defaults to false — IdP-initiated SSO (unsolicited SAML responses) is disabled by default. Set saml.allowIdpInitiated: true to restore the previous behavior. This aligns with the SAML2Int interoperability profile which recommends against IdP-initiated SSO due to its susceptibility to injection attacks.
Bug Fixes
InResponseTo validation was completely non-functional — The code read extract.inResponseTo (always undefined) instead of samlify's actual path extract.response.inResponseTo. SP-initiated InResponseTo validation now works as intended in both ACS handlers.
Audience Restriction was never validated — SAML assertions issued for a different service provider were accepted without checking the <AudienceRestriction> element. Audience is now validated against the configured samlConfig.audience value per SAML 2.0 Core §2.5.1.
SessionIndex stored as object instead of string — samlify returns sessionIndex from login responses as { authnInstant, sessionNotOnOrAfter, sessionIndex }, but the code stored the whole object. SLO session-index comparisons always failed silently. The correct inner sessionIndex string is now extracted.
Improvements
Extracted shared validateInResponseTo() and validateAudience() into packages/sso/src/saml/response-validation.ts, eliminating ~160 lines of duplicated validation logic between the two ACS handlers.
Fixed SAMLAssertionExtract type to match samlify's actual extractor output shape.
- refactor(sso)!: remove callbackUrl, consolidate ACS endpoint, fix SLO (#9117)
callbackUrl removed from samlConfig.
The ACS URL is now always derived from your baseURL and providerId. Remove callbackUrl from your SAML provider configuration. The post-login redirect destination is set per sign-in via callbackURL in signIn.sso():
await authClient.signIn.sso({ providerId: "my-provider", callbackURL: "/dashboard", });/sso/saml2/callback/:providerId endpoint removed.
Update your IdP's ACS URL to /sso/saml2/sp/acs/:providerId. This endpoint handles both GET and POST requests.
spMetadata is now optional.
You no longer need to pass spMetadata: {} when registering a provider. SP metadata is auto-generated from your configuration.
Removed unused fields from SAMLConfig:
decryptionPvk, additionalParams, idpMetadata.entityURL, idpMetadata.redirectURL. These were stored but never read. Remove them from your configuration if present.Bug fixes
- Fix SLO SessionIndex matching: LogoutRequests with a SessionIndex were silently failing to delete the correct session.
- Audience validation now defaults to the SP entity ID when audience is not configured, per SAML Core section 2.5.1.
- Restore AllowCreate in AuthnRequests, required by IdPs that use JIT provisioning.
- SP metadata endpoint now reflects actual SP capabilities (encryption, signing, SLO).
Features
- feat(auth): add user.validateUserInfo provisioning gate (#9864)
- feat(generic-oauth,sso): support IDP-initiated flows via secure bounce (#9301)
- feat(oauth): add private_key_jwt client authentication (RFC 7523) (#8836)
- feat(oauth): per-request additionalParams and loginHint (#9305)
- feat(oauth): server-trusted state channel; fix anonymous cookieless linking (#9930)
- feat(sso): support additionalFields on ssoProvider (#9445)
Bug Fixes
- fix(sso): reject OIDC endpoint redirects portably (#10072)
- fix(sso): update samlify to 2.13.1 for signed-assertion XML injection (#9821)
- fix(sso): upgrade samlify to 2.12.0 with XPath injection and XXE fixes (#9121)
- refactor(oauth): single-source Basic credentials + getHttpTestInstance (#9657)
For detailed changes, see CHANGELOG
@better-auth/mcp ✨
❗ Breaking Changes
- feat(mcp)!: ship MCP as its own package built on the OAuth provider (#9992)
The route helper is renamed requireMcpAuth (was withMcpAuth), and the remote client is createMcpResourceClient (was createMcpAuthClient). requireMcpAuth verifies the bearer token against the published JWKS and passes the verified JWT claims to your handler.
To migrate, install @better-auth/mcp, add the jwt() plugin (now required for token signing), and move options that were nested under oidcConfig to flat options on mcp({ ... }). The database models change: oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables. Regenerate or migrate your schema with npx auth migrate or npx auth generate.
- feat(oauth-provider)!: model OAuth protected resources explicitly (#9648)
validAudiences is removed. Move each existing resource identifier into resources; link clients that should be limited to specific resources through oauthClientResource or Dynamic Client Registration resources.
Access-token issuance now applies resource policy to the requested RFC 8707 resource values. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emits jti, and keeps repeated resource form parameters.
Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource refreshTokenTtl longer than refreshTokenExpiresIn will see refresh tokens expire at the provider default instead of the longer resource value.
JWT signing can now honor per-resource pins. signJWT() accepts signingKeyId and signingAlgorithm; JWKS adapters expose getKeyById() and getLatestKeyByAlg(). The jwks table adds nullable alg and crv columns, and keyPairConfigs can provision multiple algorithms in one keyring.
After upgrading, run npx @better-auth/cli generate and apply the migration before deploying. The migration adds oauthResource, oauthClientResource, and the new jwks columns. Without it, resources using signingAlgorithm cannot 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/mcp now requires an explicit resource option. 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. Existing mcp({ loginPage, consentPage }) setups should add a protected MCP resource identifier, for example resource: "https://api.example.com/mcp".
Features
- feat(oauth-provider): add DPoP support (#10039)
- feat(oauth-provider): add refresh token reuse interval (#10145)
For detailed changes, see CHANGELOG
@better-auth/scim
❗ Breaking Changes
- feat(scim)!: isolate provider connections by organization (#10249)
SCIM-managed accounts now use namespaced provider IDs (scim:{organizationId}:{providerId} or scim:{providerId} for app-level static providers). Migrate only known SCIM-managed account rows before upgrading; leave non-SCIM accounts unchanged even when they share the same provider ID.
Organization-scoped active: false now makes a user inactive in that organization while keeping SCIM group and team associations available for reactivation. Use DELETE to fully deprovision organization-scoped SCIM state.
defaultSCIM has been replaced by staticProviders. linkExistingUsers.trustedDomains has been removed; use requireExistingOrgMembership, shouldLinkUser, or explicit true instead.
- fix(scim)!: always bind personal SCIM connections to their creator (#9840)
generateSCIMToken now records the creator's userId on every personal connection. The generate-token, list-provider-connections, get-provider-connection, and delete-provider-connection endpoints grant access only to that owner. Organization-scoped connections keep their existing behavior and continue to use organization membership and the configured requiredRole checks.
This release is breaking. It removes the providerOwnership option, and owner binding can no longer be disabled. The scimProvider.userId column is now a permanent part of the schema, so run a migration after upgrading with npx auth migrate or npx auth generate.
Connections created before this release carry no owner. Access now fails closed, so those connections are no longer reachable through the management endpoints, including token regeneration. Reclaim them at the database level: delete scimProvider rows that have neither organizationId nor userId, or set userId to the intended owner, then regenerate tokens as needed. Organization-scoped connections are not affected.
Features
- feat(auth): add user.validateUserInfo provisioning gate (#9864)
- feat(scim): add durable group resources (#10018)
For detailed changes, see CHANGELOG
@better-auth/electron
❗ Breaking Changes
- fix(electron)!: enforce S256 PKCE and harden origin checks (#9645)
The Electron sign-in flow now mandates PKCE S256. Plain PKCE is rejected: the code_challenge_method parameter is gone and every authorization code is verified by hashing the verifier with SHA-256. The server no longer trusts an electron-origin header to set the request Origin. The Electron client now sends a real Origin (for example myapp:/), so upgrade the @better-auth/electron client and server together and make sure your app's scheme is in trustedOrigins. The unused disableOriginOverride option is removed.
Custom-scheme entries in trustedOrigins now match by scheme and authority instead of string prefix. A host-less entry such as myapp:// or exp:// still trusts every host of that scheme, but a host-bearing entry such as myapp://callback matches that host exactly, so it is no longer satisfied by myapp://callback.attacker.tld.
- refactor(generic-oauth)!: rewrite as first-class social provider with RFC compliance (#9069)
Breaking changes:
- signIn.oauth2({ providerId }) replaced by signIn.social({ provider })
- oauth2.link() replaced by linkSocial()
- Callback URL changed from /api/auth/oauth2/callback/:id to /api/auth/callback/:id
- genericOAuthClient() removed; generic OAuth providers now use the standard social client APIs
- pkce defaults to true (was false); set pkce: false for providers that reject PKCE
- authorizationUrlParams and tokenUrlParams only accept Record<string, string>
- issuer and requireIssuerValidation config fields removed; issuer validation is automatic via OIDC discovery
- mapProfileToUser profile typed as OAuth2UserInfo & Record<string, unknown>
For detailed changes, see CHANGELOG
@better-auth/stripe
❗ Breaking Changes
- fix(stripe)!: make onSubscriptionCancel.event required (#9531)
- fix(stripe)!: remove optional marker from onSubscriptionCancel event (#9359)
For detailed changes, see CHANGELOG
@better-auth/core
❗ Breaking Changes
- refactor(oauth)!: verify provider id_tokens with a single shared verifier (#9828)
Client-submitted id_token sign-in (signIn.social({ idToken }) and account linking) is verified by one function instead of a per-provider verifyIdToken method. Each provider declares an idToken config 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 idToken config, and the client id_token path returns ID_TOKEN_NOT_SUPPORTED. PayPal sign-in through the redirect flow is unchanged.
Custom providers that implement UpstreamProvider directly replace the removed verifyIdToken method with an idToken config:
idToken: { jwks: createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")), issuer: "https://issuer.example", audience: clientId, },For verification that cannot use a local JWKS, pass idToken: { verify: async (token, nonce) => boolean }. The verifyIdToken and disableIdTokenSignIn provider options are unchanged.
Features
- feat: add clientAssertion support to the Microsoft Entra ID social provider (#9898)
- feat(auth): add per-provider requireEmailVerification for social sign-in (#9929)
- feat(auth): add user.validateUserInfo provisioning gate (#9864)
- feat(generic-oauth,sso): support IDP-initiated flows via secure bounce (#9301)
- feat(generic-oauth): forward refreshTokenParams to token endpoint (#9948)
- feat(google): add includeGrantedScopes option (#10129)
- feat(oauth-provider): add DPoP support (#10039)
- feat(oauth): add private_key_jwt client authentication (RFC 7523) (#8836)
- feat(oauth): enforce no-store on credential responses via a declarative flag (#10065)
- feat(oauth): per-request additionalParams and loginHint (#9305)
Bug Fixes
- fix(cimd): route client_id SSRF checks through the shared host classifier (#10126)
- fix(oauth): derive redirect URI from per-request baseURL (#10127)
- fix(oauth): preserve account.scope across re-auth and refresh (#10128)
- refactor(oauth): single-source Basic credentials + getHttpTestInstance (#9657)
For detailed changes, see CHANGELOG
auth
❗ Breaking Changes
- feat(oauth)!: accumulate granted scopes as grantedScopes string[] (#9825)
Features
- feat(cli): add create-admin command (#9547)
Bug Fixes
- refactor(cli): leverage c12 v4 resolveModule for auth config loading (#9477)
- revert(oauth): remove granted scopes architecture (#10123)
For detailed changes, see CHANGELOG
@better-auth/api-key
❗ Breaking Changes
- feat(auth)!: harden atomic state transitions (#10000)
Bug Fixes
- chore: sync main to next (#9533)
For detailed changes, see CHANGELOG
@better-auth/expo
❗ Breaking Changes
- refactor(generic-oauth)!: rewrite as first-class social provider with RFC compliance (#9069)
Breaking changes:
- signIn.oauth2({ providerId }) replaced by signIn.social({ provider })
- oauth2.link() replaced by linkSocial()
- Callback URL changed from /api/auth/oauth2/callback/:id to /api/auth/callback/:id
- genericOAuthClient() removed; generic OAuth providers now use the standard social client APIs
- pkce defaults to true (was false); set pkce: false for providers that reject PKCE
- authorizationUrlParams and tokenUrlParams only accept Record<string, string>
- issuer and requireIssuerValidation config fields removed; issuer validation is automatic via OIDC discovery
- mapProfileToUser profile typed as OAuth2UserInfo & Record<string, unknown>
For detailed changes, see CHANGELOG
@better-auth/cimd ✨
Features
- feat(cimd): add Client ID Metadata Document plugin (#9159)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Features
- feat(drizzle-adapter): support Drizzle Relations v2 (#9489)
For detailed changes, see CHANGELOG
@better-auth/i18n
Features
- feat(i18n): add built-in translations for 22 languages (#9157)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@adrianmxb, @app/better-release, @brentmitchell25, @bytaesu, @GautamBytes, @gustavovalverde, @ItalyPaleAle, @OscarCornish, @pi0, @ping-maxwell, @ruban-s, @sovetski, @yordisFull changelog: v1.6.22...v1.7.0-rc.0
Original source - Jun 26, 2026
- Date parsed from source:Jun 26, 2026
- First seen by Releasebot:Jun 27, 2026
v1.7.0-beta.10
Better Auth ships a broad beta update with refreshed bundled dependencies, stronger security and authorization checks, tighter OAuth, SAML, SIWE and 2FA handling, plus fixes across adapters and SCIM. It also adds Drizzle Relations v2 support and refresh token reuse controls for MCP and OAuth provider flows.
better-auth
Bug Fixes
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.
Fixed rate limiting to be applied before plugin request handlers run (#10191)
Fixed unproven credentials to be revoked when signing in via magic link or email OTP (#10239)
Fixed account-linking logs to be routed through the configured logger (#10121)
Fixed admin authorization to use authoritative session reads (#10187)
Fixed TypeScript inference errors by declaring inherited APIError properties (#8734)
Fixed server-side OAuth requests to no longer follow redirects (#10241)
Fixed the schema option in device authorization to be optional under Zod v4 (#9939)
Fixed hosted-domain validation to be applied consistently across all Google sign-in flows (#10197)
Fixed OAuth proxy to reject profile callbacks when OAuth state is missing or expired (#10183)
Fixed OAuth provider profiles to respect user input rules (#10196)
Fixed PayPal userinfo subject to be bound to the verified ID token subject (#10192)
Fixed refresh cookie Max-Age to be capped at the configured expiresIn value (#9621)
Fixed SIWE sign-in to reject when the provided email already belongs to another account (#10228)
Fixed TOTP and backup code verification to cap the number of allowed attempts (#10210)
Fixed username storage to only accept valid displayUsername fallbacks (#10182)
For detailed changes, see CHANGELOG
@better-auth/sso
Bug Fixes
Fixed SSO provider deletion to also remove associated linked account rows (#10224)
Fixed SSO provider domain verification to require DNS proof for every listed domain (#10227)
Fixed SAML SLO POST form action to be restricted to http and https schemes (#10225)
Fixed SAML response binding to be validated against the Service Provider configuration (#10226)
For detailed changes, see CHANGELOG
auth
Bug Fixes
Fixed disableMigration to be honored for plugin schema tables (#10198)
Fixed generated BETTER_AUTH_SECRET to use 32 characters instead of 16 (#10186)
Fixed two-factor verification to enforce account-level lockout after repeated failed attempts (#10240)
For detailed changes, see CHANGELOG
@better-auth/api-key
Bug Fixes
Fixed client IP resolution from forwarded headers to be more robust (#10203)
Refactored IP resolution logic into a shared core utility (#10216)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Features
Added support for Drizzle Relations v2 via a new @better-auth/drizzle-adapter/relations-v2 entry point (#9489)
For detailed changes, see CHANGELOG
@better-auth/i18n
Bug Fixes
Fixed the English language fallback and updated i18n documentation (#9872)
For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
Fixed the Kysely adapter to return null when an update matches no rows (#10180)
For detailed changes, see CHANGELOG
@better-auth/mcp
Features
Added a refreshTokenReuseInterval option, defaulting to 30 seconds, so native/public clients can retry a refresh if a prior token rotation raced against it (#10145)
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
Features
Added refreshTokenReuseInterval to allow the OAuth provider to replay refresh token responses for duplicate requests within a configurable time window (#10145)
For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
Fixed SCIM write operations to be properly scoped and to honor the active attribute (#10242)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@adityachaudhary99, @Bekacru, @benpsnyder, @bytaesu, @dipan-ck, @gustavovalverde, @moonevm, @Paola3stefania, @ping-maxwell, @rachit367, @sleepe229, @WilsonnnTan
Full changelog: v1.7.0-beta.9...v1.7.0-beta.10
Original source - Jun 26, 2026
- Date parsed from source:Jun 26, 2026
- First seen by Releasebot:Jun 27, 2026
v1.6.22
Better Auth fixes credential revocation, hardens server-side OAuth redirects, and improves SCIM, Stripe, and two-factor authentication safeguards in a security-focused release.
better-auth
Bug Fixes
- Fixed unproven credentials not being revoked during magic link and email OTP sign-in (#10239)
- Fixed server-side OAuth requests to refuse redirect responses instead of following them (#10241)
For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
- Fixed SCIM write-path operations to be properly scoped and to correctly honor the active attribute (#10242)
For detailed changes, see CHANGELOG
@better-auth/stripe
Bug Fixes
- Fixed organization subscription actions (cancel, upgrade, restore, and the billing portal) that could act on the wrong organization.
For detailed changes, see CHANGELOG
auth
Bug Fixes
- Added account-level verification lockout for two-factor authentication (#10240)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
- @gustavovalverde
Full changelog: v1.6.21...v1.6.22
Original source Similar to Better Auth with recent updates:
- xAI release notes139 release notes · Latest Jul 6, 2026
- Anthropic release notes695 release notes · Latest Jul 8, 2026
- OpenAI release notes810 release notes · Latest Jul 7, 2026
- Vercel release notes1819 release notes · Latest Jul 8, 2026
- Vercel Labs release notes281 release notes · Latest Jul 7, 2026
- Smokeball release notes135 release notes · Latest Jul 2, 2026
- Jun 26, 2026
- Date parsed from source:Jun 26, 2026
- First seen by Releasebot:Jun 26, 2026
v1.6.21
Better Auth fixes rate limiting, admin permission updates, OAuth and SSO checks, two-factor lockouts, and several sign-in and billing edge cases, while tightening API key IP handling and adapter behavior for more reliable auth flows.
better-auth
Bug Fixes
Fixed rate limits to be enforced before plugin request handlers run (#10191)
Fixed admin permission changes and bans to take effect immediately, even when session cookie cache is enabled (#10187)
Fixed deviceAuthorization() throwing a ZodError when called without a schema option under Zod v4 (#9939)
Fixed Google hosted-domain validation to apply consistently across all sign-in flows, including Google One Tap (#10197)
Fixed OAuth proxy to reject profile callbacks that do not match an issued OAuth state, preventing session creation with stale state (#10183)
Fixed OAuth sign-up and account linking to ignore provider profile values for fields marked input: false (#10196)
Fixed PayPal sign-in to validate user info against the verified ID token subject (#10192)
Fixed SIWE sign-in to reject emails that already belong to another account, preventing one email from being attached to two accounts (#10228)
Fixed two-factor verification to lock out after five wrong codes for TOTP and backup codes, returning TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE (#10210)
Fixed the username plugin to only store displayUsername fallbacks that pass username validation during email sign-up (#10182)
For detailed changes, see CHANGELOG@better-auth/sso
Bug Fixes
Fixed SSO provider deletion to also remove linked accounts, preventing reuse by a later provider with the same ID (#10224)
Fixed SSO domain verification to require DNS proof for every domain listed on a provider (#10227)
Fixed SAML single logout to reject IdP SLO POST URLs that use non-http(s) schemes such as javascript: or data: (#10225)
Fixed SAML SSO to reject responses whose audience, recipient, or destination does not match the configured Service Provider (#10226)
For detailed changes, see CHANGELOG@better-auth/api-key
Bug Fixes
Fixed client IP resolution to prevent X-Forwarded-For spoofing in multi-hop proxy chains (#10203)
Refactored request IP resolution into a centralized core resolver (#10216)
For detailed changes, see CHANGELOGauth
Bug Fixes
Fixed disableMigration: true to be respected on plugin schema tables during generation and runtime migration (#10198)
Fixed the CLI to generate BETTER_AUTH_SECRET values with 32 characters instead of 16 (#10186)
For detailed changes, see CHANGELOG@better-auth/kysely-adapter
Bug Fixes
Fixed adapter.update to return null when no matching row is found (#10180)
For detailed changes, see CHANGELOG@better-auth/stripe
Bug Fixes
Fixed organization subscription actions (cancel, upgrade, restore, and the billing portal) that could act on the wrong organization.
For detailed changes, see CHANGELOGContributors
Thanks to everyone who contributed to this release:
@Bekacru, @benpsnyder, @bytaesu, @gustavovalverde, @moonevm, @Paola3stefania, @ping-maxwell, @rachit367Full changelog: v1.6.20...v1.6.21
Original source - Jun 20, 2026
- Date parsed from source:Jun 20, 2026
- First seen by Releasebot:Jun 20, 2026
v1.7.0-beta.9
Better Auth ships tighter OIDC and OAuth provider controls with breaking claim restrictions, confidential DCR support for PKCE-free authorization code flows, and new claims.userinfo request support. It also brings a broad set of UserInfo, token, refresh token, and authorization flow fixes.
@better-auth/oauth-provider
❗ Breaking Changes
Restricted customIdTokenClaims, extension ID-token claims, and per-issuance idTokenClaims from overriding protected OIDC/JWT protocol claims (#10140)
Migration: Remove any iss, sub, aud, exp, nonce, auth_time, acr, amr, or azp fields from customIdTokenClaims, extension ID-token claims, and per-issuance idTokenClaims. Use namespaced custom claims (e.g., "https://example.com/role") for application-specific data instead.
Features
Added support for confidential DCR clients to complete authorization-code flows without PKCE when clientRegistrationRequirePKCE: false is set (#10146)
Added support for the claims.userinfo authorization request parameter, allowing clients to request specific standard claims from the UserInfo endpoint (#10156)
Bug Fixes
Fixed the UserInfo endpoint to accept bearer tokens in application/x-www-form-urlencoded POST request bodies (#10155)
Fixed confidential clients that opted out of PKCE to successfully request offline_access when the authorization includes both openid scope and a nonce (#10153)
Fixed the OIDC authorization endpoint to accept form-encoded POST requests and return proper errors for unsupported request and request_uri parameters (#10151)
Fixed the UserInfo endpoint to correctly return profile and email scope claims, and added rejection of unsupported acr_values in authorization requests (#10152)
Fixed the token endpoint to only require redirect_uri when the original authorization request included one, and to return invalid_grant on mismatches (#10159)
Fixed Dynamic Client Registration to preserve client key metadata across updates (#10144)
Fixed authorization requests missing response_type to redirect errors to the client redirect URI instead of the provider error page (#10149)
Fixed authorization code replay to correctly return invalid_grant and revoke all tokens previously issued from the replayed code (#10150)
Fixed refresh token validation to return invalid_grant when a client attempts to use a refresh token issued to a different client (#10154)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@gustavovalverde
Full changelog: v1.7.0-beta.8...v1.7.0-beta.9
Original source - Jun 20, 2026
- Date parsed from source:Jun 20, 2026
- First seen by Releasebot:Jun 20, 2026
v1.6.20
Better Auth fixes account-linking logs, TypeScript inference, and refresh cookie expiry, with i18n fallback improvements.
better-auth
Bug Fixes
- Fixed account-linking logs to route through the configured logger (#10121)
- Fixed TypeScript inference errors by declaring inherited APIError properties (#8734)
- Fixed refresh cookie Max-Age to be capped at expiresIn (#9621)
For detailed changes, see CHANGELOG
@better-auth/i18n
Bug Fixes
- Fixed English language fallback behavior and improved i18n documentation (#9872)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@adityachaudhary99, @dipan-ck, @sleepe229, @WilsonnnTan
Full changelog: v1.6.19...v1.6.20
Original source - Jun 18, 2026
- Date parsed from source:Jun 18, 2026
- First seen by Releasebot:Jun 18, 2026
v1.7.0-beta.8
Better Auth ships bug fixes and a Google OAuth update, improving account creation rollback, redirect URI handling in multi-host deployments, scope preservation during re-authentication and refresh, and user data retention during account linking. It also adds an includeGrantedScopes option for Google.
better-auth
Bug Fixes
Fixed OAuth account creation to roll back the user row when account writes fail (#10125)
Fixed redirect URI derivation to use the per-request base URL in multi-host deployments (#10127)
Fixed OAuth scope preservation so previously granted scopes are not lost during re-authentication or token refresh (#10128)
Fixed user data preservation when overrideUserInfo returns null during account linking (#10124)
For detailed changes, see CHANGELOG
@better-auth/core
Features
Added includeGrantedScopes option to the Google provider to control scope accumulation across OAuth flows (#10129)
Bug Fixes
Fixed redirect URI derivation to use the per-request base URL in multi-host deployments (#10127)
Fixed OAuth scope preservation so previously granted scopes are not lost during re-authentication or token refresh (#10128)
For detailed changes, see CHANGELOG
auth
Bug Fixes
Reverted the granted scopes OAuth architecture (#10123)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@gustavovalverde
Full changelog: v1.7.0-beta.7...v1.7.0-beta.8
Original source - Jun 18, 2026
- Date parsed from source:Jun 18, 2026
- First seen by Releasebot:Jun 18, 2026
v1.7.0-beta.7
Better Auth adds refreshTokenParams support for token refresh, fixes ID token nonce binding in the generic OAuth redirect flow, strengthens SSRF host validation, and expands ID token claims with sessionId for better OAuth and session handling.
better-auth
Features
- Added refreshTokenParams config to forward extra parameters to the token endpoint during token refresh (#9948)
Bug Fixes
- Fixed ID token nonce binding in the generic OAuth redirect flow (#10095)
For detailed changes, see CHANGELOG
@better-auth/core
Features
- Added refreshTokenParams config to forward extra parameters to the token endpoint during token refresh (#9948)
Bug Fixes
- Fixed SSRF protection for client_id host validation by routing checks through the shared host classifier, blocking additional non-public address forms (#10126)
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
Features
- Added sessionId to ID token claim contributors, accessible via input.sessionId in claims.idToken and claims.accessToken contributors (#10113)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
- @gustavovalverde, @yordis
Full changelog: v1.7.0-beta.6...v1.7.0-beta.7
Original source - Jun 16, 2026
- Date parsed from source:Jun 16, 2026
- First seen by Releasebot:Jun 17, 2026
v1.7.0-beta.6
Better Auth ships a broad beta release with major OAuth and MCP changes, stronger security controls, new DPoP support, popup sign-in, device code pre-binding, and many reliability and TypeScript fixes across core plugins and adapters.
better-auth
❗ Breaking Changes
Added wildcard endpoint matching to the captcha plugin, requiring full auth path matches instead of partial prefix matching (#10004)
Migration: Replace partial endpoint paths like /sign-in with explicit wildcards such as /sign-in/* or /sign-in/** in your captcha plugin configuration.
Moved the MCP plugin into its own package, @better-auth/mcp, built on @better-auth/oauth-provider (#9992)
Migration: Install @better-auth/mcp, add the jwt() plugin, update imports from better-auth/plugins to @better-auth/mcp, rename withMcpAuth to requireMcpAuth and createMcpAuthClient to createMcpResourceClient, then run npx auth migrate to apply schema changes (oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables).
Introduced explicit OAuth protected resource modeling, replacing validAudiences with a resource-first configuration API (#9648)
Migration: Replace validAudiences with resources, link clients through oauthClientResource, then run npx @better-auth/cli generate and apply the migration to add oauthResource, oauthClientResource, and new jwks columns before deploying.
Changed dynamic baseURL resolution to ignore x-forwarded-host by default, preventing forwarded headers from selecting an unintended allowed host (#9134)
Migration: If your proxy exposes the public hostname only through x-forwarded-host, add advanced: { trustedProxyHeaders: true } to your betterAuth() config. Deployments where the proxy rewrites Host directly (nginx default, Vercel, Cloudflare, Netlify) are unaffected.
Required a Google client ID to be configured for One Tap ID token audience validation (#10036)
Migration: Configure oneTap({ clientId: "your-google-client-id" }) or set socialProviders.google.clientId in your Better Auth config.
Removed the deprecated oidcProvider plugin from better-auth/plugins (#10031)
Migration: Replace oidcProvider from better-auth/plugins with @better-auth/oauth-provider for OIDC authorization-server integrations.
Features
Added support for pre-binding device codes to a specific user during the device authorization flow (#9995)
Added a popup-based OAuth sign-in flow as an alternative to full-page redirects (#9890)
Added DPoP (RFC 9449) sender-constrained access token support to the OAuth provider (#10039)
Enforced Cache-Control: no-store on all OAuth credential responses to prevent proxies and browsers from caching tokens (#10065)
Added auth.api.consumePhoneNumberOTP for verifying and consuming phone OTP codes server-side without modifying users or sessions (#9766)
Added opt-in JWKS-backed asymmetric JWT support for session cookie cache tokens, allowing public-key verification instead of shared secrets (#8931)
Bug Fixes
Fixed session checks to work without HTTP headers for server-side use cases (#10053)
Fixed the cookie cache fallback to correctly retrieve sessions on a cache miss (#9348)
Fixed sendVerificationEmail errors to propagate to callers instead of being silently swallowed (#8863)
Fixed stateless account cookies to resolve correctly across multiple server instances (#9979)
Fixed setUserPassword in the admin plugin to create a credential account when one does not already exist (#9482)
Fixed TypeScript inference in updateSession to include plugin-defined session fields (#9777)
Fixed auth client return types to use named types instead of anonymous inline shapes (#10071)
Fixed plugin type inference to work correctly in composite TypeScript monorepo setups (#9583)
Fixed large session and account cookies to be chunked near the browser size limit, preventing truncation (#10088)
Fixed the database layer to reuse active transactions instead of opening nested ones (#10070)
Fixed last-login-method cookie clearing to include the domain attribute, ensuring cross-subdomain cookies are removed correctly (#9319)
Fixed the OAuth popup flow to filter internal state keys from the additionalData returned to the caller (#10067)
Fixed OpenAPI schema generation to mark model ID fields as required (#9704)
Fixed OpenAPI serialization of Zod request schemas to accurately reflect all validation constraints (#9315)
Fixed updateMemberRole to reject unknown or empty role values with a proper error (#9962)
Refactored role authorization logic to reduce nesting and improve extensibility (#9677)
Reverted the headerless session check fix (#10053) pending further investigation (#10074)
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
❗ Breaking Changes
Moved the MCP plugin into its own package, @better-auth/mcp, built on @better-auth/oauth-provider (#9992)
Migration: Install @better-auth/mcp, add the jwt() plugin, update imports from better-auth/plugins to @better-auth/mcp, rename withMcpAuth to requireMcpAuth and createMcpAuthClient to createMcpResourceClient, then run npx auth migrate to apply schema changes (oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables).
Introduced explicit OAuth protected resource modeling, replacing validAudiences with a resource-first configuration API (#9648)
Migration: Replace validAudiences with resources, link clients through oauthClientResource, then run npx @better-auth/cli generate and apply the migration to add oauthResource, oauthClientResource, and new jwks columns before deploying.
Changed client authentication strategies to return only the proven client ID, with the provider resolving and authorizing the client record itself (#10063)
Migration: Update custom clientAuthentication strategies to return { clientId } instead of { clientId, client }, and remove the grantType argument from provider.authenticateClient() calls in custom grant handlers.
Features
Added DPoP (RFC 9449) sender-constrained access token support to the OAuth provider (#10039)
Added an extension surface for registering custom token grants, client authentication methods, discovery metadata, and claim contributors in companion plugins (#10030)
Improved token introspection to return consistent claims for both opaque and JWT access tokens, and to support resource-server-scoped introspection (#10045)
Added protected dynamic client registration (RFC 7591) with initial access token support, allowing machine clients to register without a user session (#10037)
Enforced Cache-Control: no-store on all OAuth credential responses to prevent proxies and browsers from caching tokens (#10065)
Bug Fixes
Fixed multiple bugs: JWKS caching, OAuth ID mapping, team invitations, account cookie handling, and SCIM deprovisioning (#9987)
Fixed OAuth query parameter canonicalization for signed requests to prevent signature verification failures (#9941)
Fixed /oauth2/userinfo to return a WWW-Authenticate challenge when the access token is invalid, expired, or revoked (#10068)
For detailed changes, see CHANGELOG
@better-auth/mcp ✨
❗ Breaking Changes
Moved the MCP plugin into its own package, @better-auth/mcp, built on @better-auth/oauth-provider (#9992)
Migration: Install @better-auth/mcp, add the jwt() plugin, update imports from better-auth/plugins to @better-auth/mcp, rename withMcpAuth to requireMcpAuth and createMcpAuthClient to createMcpResourceClient, then run npx auth migrate to apply schema changes (oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables).
Introduced explicit OAuth protected resource modeling, replacing validAudiences with a resource-first configuration API (#9648)
Migration: Add a resource option to your mcp({ ... }) configuration (for example, resource: "https://api.example.com/mcp"), replace validAudiences with resources, then run npx @better-auth/cli generate and apply the migration before deploying.
Features
Added DPoP (RFC 9449) sender-constrained access token support to the OAuth provider (#10039)
For detailed changes, see CHANGELOG
@better-auth/api-key
❗ Breaking Changes
Hardened state transitions with atomic adapter primitives, enforcing API key quotas and rate limits without race conditions (#10000)
Migration: Custom adapters must implement the new incrementOne primitive natively. Custom BetterAuthRateLimitStorage implementations must replace the separate get/set pattern with a single atomic consume method.
Bug Fixes
Hardened session authority checks to reject stale cookie cache entries and unverified session selectors (#9991)
Refactored server-only endpoints to prevent them from being accidentally exposed over HTTP (#9835)
For detailed changes, see CHANGELOG
auth
Bug Fixes
Fixed the generate command to correctly handle a directory path passed to --output (#9564)
Fixed CLI import resolution for SvelteKit, Vite asset, and Cloudflare virtual-module imports (#9834)
Fixed the Drizzle schema generator to correctly serialize array defaultValue for additionalField definitions (#10048)
Fixed the Prisma schema regenerator to skip Unsupported() field types without failing (#10011)
Fixed the Prisma generator to update existing field types when regenerating the schema (#9729)
For detailed changes, see CHANGELOG
@better-auth/sso
Features
Added additionalFields support to ssoProvider for storing and returning custom SSO provider data (#9445)
Bug Fixes
Hardened identity token validation for One Tap, Microsoft, SSO, WeChat, and Reddit providers (#10003)
Fixed OIDC endpoint redirect rejection to work portably, including on Cloudflare Workers, with a clear error when a discovery or token endpoint redirects (#10072)
For detailed changes, see CHANGELOG
@better-auth/core
Features
Added DPoP (RFC 9449) sender-constrained access token support to the OAuth provider (#10039)
Enforced Cache-Control: no-store on all OAuth credential responses to prevent proxies and browsers from caching tokens (#10065)
For detailed changes, see CHANGELOG
@better-auth/expo
Bug Fixes
Hardened trusted request context handling to prevent spoofed request contexts in Expo apps (#9990)
Fixed the Expo linkSocial flow to send the session cookie when linking an account via an ID token (#9953)
For detailed changes, see CHANGELOG
@better-auth/scim
Features
Added durable SCIM Group resources with organization-scoped membership, role projection, and Group lifecycle endpoints (#10018)
Bug Fixes
Fixed team capacity enforcement, made SCIM token comparison constant-time, and fixed org-admin SSO domain verification (#10002)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
Fixed the Drizzle MySQL adapter to return rows consumed by update and delete operations (#10081)
For detailed changes, see CHANGELOG
@better-auth/electron
Bug Fixes
Restructured the session fetch architecture in the Electron client to improve reliability (#8760)
For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
Made single-use credential, counter, and replay marker operations atomic to prevent race conditions (#9993)
For detailed changes, see CHANGELOG
@better-auth/mongo-adapter
Bug Fixes
Fixed guarded state transitions to be portable across Prisma-backed adapter configurations (#10086)
For detailed changes, see CHANGELOG
@better-auth/passkey
Bug Fixes
Fixed passkey OpenAPI schema generation to produce valid schemas compatible with client code generators (#9555)
For detailed changes, see CHANGELOG
@better-auth/stripe
Bug Fixes
Fixed Stripe subscription handling and customer linking to correctly associate subscriptions with users (#9971)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@arnnvv, @Bekacru, @brentmitchell25, @brone1323, @bytaesu, @ChrisMGeo, @ElGauchooooo, @GautamBytes, @gustavovalverde, @ping-maxwell, @SferaDev, @tsushanth, @Tushar-Khandelwal-2004
Full changelog: v1.7.0-beta.5...v1.7.0-beta.6
Original source - Jun 16, 2026
- Date parsed from source:Jun 16, 2026
- First seen by Releasebot:Jun 16, 2026
v1.6.19
Better Auth ships v1.6.19 with device authorization improvements, stronger session and cookie handling, and a set of fixes across auth, Drizzle, Mongo, passkey, and SCIM. The release also improves TypeScript output, verification flows, and OpenAPI compatibility.
better-auth
Features
- Added support for pre-binding device codes to a specific user in the device authorization plugin (#9995)
Bug Fixes
- Fixed headerless session checks (#10053)
- Fixed cookie cache fallback lookup (#9348)
- Fixed sendVerificationEmail errors not being surfaced to the client (#8863)
- Fixed auth client return types not being emitted correctly in TypeScript declaration builds (#10071)
- Fixed session and account cache cookies being silently dropped when near the browser's per-cookie size limit by splitting them into chunks (#10088)
- Fixed single-use verification flows (such as magic-link) hanging on connection-limited database adapters by reusing active transactions (#10070)
- Fixed the domain not being included when clearing cross-subdomain cookies in the last-login-method plugin (#9319)
- Fixed the oauth-popup plugin leaking internal OAuth state keys into additionalData (#10067)
- Reverted the headerless session check fix (#10074)
For detailed changes, see CHANGELOG
auth
Bug Fixes
- Fixed the generate command not handling a directory path passed to --output (#9564)
- Fixed array additionalField default values not being serialized correctly in the Drizzle schema generator (#10048)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
- Fixed password reset tokens not working with the Drizzle MySQL adapter after being consumed (#10081)
For detailed changes, see CHANGELOG
@better-auth/mongo-adapter
Bug Fixes
- Fixed guarded state transitions (token rotation, revocation, two-factor backup-code regeneration, device-code claiming, and organization invitation acceptance) failing on Prisma and on MongoDB servers older than 5.0 (#10086)
For detailed changes, see CHANGELOG
@better-auth/passkey
Bug Fixes
- Fixed invalid OpenAPI output for callback, session, and passkey routes so client generators can consume the schema (#9555)
For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
- Stopped logging SCIM user filter values when listing users (#10087)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@brone1323, @bytaesu, @ChrisMGeo, @ElGauchooooo, @gustavovalverde, @ping-maxwell, @tsushanth, @Tushar-Khandelwal-2004
Full changelog: v1.6.18...v1.6.19
Original source - Jun 12, 2026
- Date parsed from source:Jun 12, 2026
- First seen by Releasebot:Jun 16, 2026
v1.6.18
Better Auth fixes a wide range of auth, session, and adapter issues, tightening concurrency safety, rate limiting, token replay protection, and session handling while improving OAuth, SSO, SCIM, passkeys, and storage reliability.
better-auth
Bug Fixes
- Fixed getCookieCache to return null for expired sessions instead of treating stale signed cookies as live sessions.
- Fixed the delete-account confirmation link to prevent duplicate account deletions from concurrent callback requests.
- Fixed one-time tokens from being redeemable multiple times under concurrent requests.
- Fixed password reset tokens from changing a password more than once under concurrent requests.
- Fixed Reddit sign-in to assign a non-routable placeholder address (
<id>@reddit.invalid) to users with no email, preventing accidental matches with real mailboxes. - Fixed Sign-In with Ethereum nonces from being accepted multiple times under concurrent sign-in requests.
- Added internalAdapter.reserveVerificationValue to atomically record single-use markers, ensuring only one concurrent caller succeeds for replay-protected operations.
- Added the incrementOne adapter method and SecondaryStorage.increment for atomic counter updates, enabling strict rate-limit and usage-counter enforcement under concurrent load.
- Fixed expired two-factor challenges from completing login and prevented duplicate session creation from concurrent verifications.
- Fixed captcha verification to time out after 10 seconds, preventing slow or unreachable captcha providers from hanging requests indefinitely.
- Fixed /delete-user/callback to reject account deletion when the session has been revoked server-side (cookie-only session deployments are unaffected).
- Fixed rate limiting to prevent concurrent requests from slipping past configured limits, with a new optional consume method for custom storage backends to opt into strict enforcement.
- Fixed team deletion to preserve pending invitations by removing only the deleted team's reference rather than invalidating the invitations entirely.
- Fixed expected authentication validation failures to log as warnings instead of errors.
- Fixed MCP bearer token validation to reject expired access tokens and require the offline_access scope for refresh token usage.
- Fixed plugin API inference in composite monorepo setups where the core package resolved through multiple paths (#9583)
- Fixed OpenAPI generation to accurately serialize Zod request schemas, including optional, nullable, intersected, and record-shaped types (#9315)
- Fixed a memory leak where the JWKS cache could grow on every access token verification.
- Fixed Google One Tap to require a configured client ID (set via the oneTap plugin or socialProviders.google) and reject tokens issued for other applications.
- Fixed device-authorization token polling to prevent the same approved device code from being redeemed multiple times under concurrent polls.
- Fixed account cookie preservation when switching users in the same browser session.
- Fixed email OTP sign-in to prevent concurrent requests from signing in multiple times or exceeding the attempt limit.
- Fixed phone-number OTP sign-in to prevent concurrent requests from signing in multiple times or exceeding the attempt limit.
- Fixed two-factor OTP sign-in to prevent concurrent requests from signing in multiple times or exceeding the attempt limit.
- Fixed the Have I Been Pwned plugin to check breached passwords on additional endpoints, including email-OTP and phone-number reset-password routes and admin password-setting routes.
- Fixed the multi-session set-active and revoke endpoints to only act on sessions the caller holds a signed cookie for, preventing unauthorized session manipulation.
- Fixed the OIDC /oauth2/endsession endpoint to reject cross-site logout requests that carry only a session cookie without a valid id_token_hint.
- Fixed WeChat sign-in to work without an email address by assigning a stable placeholder email, with mapProfileToUser available to supply a real one.
- For detailed changes, see CHANGELOG
@better-auth/sso
Bug Fixes
- Fixed SAML assertion replay protection to hold under concurrent requests, preventing a duplicate submission from being accepted more than once.
- Fixed organization admins and owners to verify domain ownership for SSO providers their organization owns, not just the member who originally registered the provider.
- Fixed trustEmailVerified to treat only a boolean true or the string "true" as a verified email, rejecting the string "false" as unverified.
- For detailed changes, see CHANGELOG
@better-auth/memory-adapter
Bug Fixes
- Fixed the memory adapter to not discard concurrent writes when a transaction fails, and made update and delete no-ops on empty filters instead of modifying every row.
- Fixed counter updates on the memory, Kysely, Drizzle, Prisma, and MongoDB adapters to be atomic on the default configuration, preventing race conditions in rate limiting and API-key usage limits.
- For detailed changes, see CHANGELOG
@better-auth/oauth-provider
Bug Fixes
- Fixed signed OAuth redirect parameters to be canonicalized by key and value, preventing CDN or proxy reordering from breaking signature verification (#9941)
- Fixed token introspection and revocation endpoints to cache signing keys per auth instance rather than fetching them from the database on every request.
- For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
- Fixed organization-scoped SCIM deletes to remove user membership through the organization adapter, so team memberships and member-removal hooks are applied correctly.
- Fixed SCIM bearer token comparison to use constant-time comparison during request authentication, closing a timing side channel across all storage modes.
- For detailed changes, see CHANGELOG
@better-auth/api-key
Bug Fixes
- Fixed concurrent API key verification to prevent the remaining-uses count from going below zero or the rate limit from being exceeded.
- For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
- Fixed updateMany to return the number of rows it affected, as the adapter contract specifies.
- For detailed changes, see CHANGELOG
@better-auth/electron
Bug Fixes
- Fixed Electron authorization codes from being exchangeable for a session more than once under concurrent exchange attempts.
- For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
- Fixed SQLite mutations through the Bun and Node drivers to correctly report affected row counts and inserted row IDs, fixed multi-parameter binding on the Bun driver, and fixed consumeOne compatibility with SQL Server.
- For detailed changes, see CHANGELOG
@better-auth/passkey
Bug Fixes
- Fixed passkey challenge validation to reject cross-purpose challenges, preventing an authentication challenge from being used to complete registration and vice versa.
- For detailed changes, see CHANGELOG
@better-auth/prisma-adapter
Bug Fixes
- Fixed the Prisma adapter's delete operation to surface errors instead of silently reporting success when the failure is not a missing-record error.
- For detailed changes, see CHANGELOG
@better-auth/redis-storage
Bug Fixes
- Fixed Redis-backed rate-limit windows to set expiry only when the window first opens, preventing continued traffic from extending the window, and added an atomic increment method for strict enforcement.
- For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
- @GautamBytes
Full changelog: v1.6.17...v1.6.18
Original source - Jun 12, 2026
- Date parsed from source:Jun 12, 2026
- First seen by Releasebot:Jun 16, 2026
v1.6.17
Better Auth releases a hardening-focused update with an experimental oauthPopup plugin for popup-based OAuth sign-in, plus stronger concurrent request handling, atomic counters and replay protection, and broad fixes across OAuth, sessions, SSO, SCIM, Expo, adapters, and Stripe.
better-auth
Features
Added an experimental oauthPopup plugin for popup-based OAuth sign-in, enabling sign-in inside cross-site iframes by completing the OAuth flow in a popup and passing the session token back via the bearer plugin (#9890)
Bug Fixes
Fixed getCookieCache to return null for an expired session instead of stale data, so middleware no longer treats an expired signed cookie as a live session.
Fixed a race condition where a delete-account confirmation link could delete the account more than once when its callback was opened concurrently.
Fixed a race condition where a one-time token could be redeemed for a session more than once when redeemed concurrently.
Fixed a race condition where a password reset token could change the password more than once when used from concurrent requests.
Fixed Reddit sign-in to assign a non-routable placeholder address (<id>@reddit.invalid) to users with no email, instead of one on the real reddit.com domain, preventing accidental mailbox matches. The address stays unverified, and mapProfileToUser can supply a real email.
Fixed Sign-In with Ethereum to prevent a nonce from being used to sign in more than once when submitted from concurrent requests.
Added internalAdapter.reserveVerificationValue for atomic single-use markers, ensuring exactly one concurrent caller succeeds and the rest see the marker as already taken, hardening replay protection across all verification flows. Database-backed storage is atomic; secondary-storage-only mode is best-effort.
Added the optional incrementOne adapter method and SecondaryStorage.increment for atomic counter updates with conditional row guards, enabling strict enforcement of rate limits and usage counters. Adapters without native support fall back to a transaction-based approach.
Fixed expired two-factor sign-in challenges from completing login, and prevented the same challenge from creating more than one session when verified concurrently.
Fixed captcha provider verification to time out after 10 seconds and fail closed, preventing a slow or unreachable provider from blocking requests indefinitely.
Fixed /delete-user/callback to reject account deletion when the session has been revoked server-side, instead of proceeding within the cookie-cache window. Deployments that keep sessions only in the cookie are unaffected.
Fixed concurrent requests from slipping past the configured rate limit, resolved unbounded memory growth in the in-memory rate-limit store, and made the database backend remove expired entries automatically. A custom rate-limit storage may implement a new optional consume method for strict enforcement.
Fixed team deletion to preserve pending invitations, which now drop the removed team and remain valid for their remaining teams or as organization-level invitations.
Downgraded expected auth validation failures from error logs to warnings.
Fixed expired MCP access tokens from being accepted, and restricted refresh token acceptance to authorizations that included the offline_access scope.
Fixed team member limits to be enforced on addMember and add-team-member paths, preventing teams from exceeding their maximumMembersPerTeam cap, and ensured a rejected addMember does not create the organization member (#10002)
Fixed generic OAuth sign-in for providers whose userinfo response lacks a sub or id field when mapProfileToUser derives the account id (#9987)
Fixed single-use credentials, counters, and replay markers to be handled atomically under concurrent requests (#9993)
Fixed stateless OAuth deployments to correctly read account info and tokens when different server instances handle sign-in and subsequent requests (#9979)
Fixed admin.setUserPassword to create a credential account for users who only have social or magic-link accounts, enabling direct password assignment without manually modifying the account table (#9482)
Fixed updateSession to accept custom session fields inferred from inferAdditionalFields (#9777)
Fixed duplicate /get-session requests triggered by focus and other browser events, stabilized client hook data references to reduce unnecessary re-renders, and resolved session state getting stuck loading after unmounting during an in-flight request (#8760)
Fixed the OpenAPI schema to mark model id fields as required (#9704)
Fixed updateMemberRole to reject unknown or malformed role values, validating them against configured static and dynamic roles (#9962)
Fixed a memory leak where the JWKS cache grew on every access token verification.
Fixed Google One Tap to require a configured client ID and reject ID tokens issued for a different application, preventing unauthorized sign-ins.
Fixed a race condition where polling for a device-authorization token could redeem the same approved device code more than once.
Fixed account cookie handling when switching users in the same browser, preserving the fresh cookie instead of expiring it from stale request state.
Refactored role.authorize control flow without changing existing authorization behavior (#9677)
Fixed a race condition where submitting the same email OTP from concurrent requests could sign in more than once or exceed the attempt limit.
Fixed a race condition where submitting the same phone-number OTP from concurrent requests could sign in more than once or exceed the attempt limit.
Fixed a race condition where submitting the same two-factor OTP from concurrent requests could sign in more than once or exceed the attempt limit.
Improved the Have I Been Pwned plugin to check submitted passwords on more endpoints by default, including email-OTP and phone-number reset-password routes and admin create-user and set-user-password routes.
Fixed multi-session set-active and revoke endpoints to only act on sessions the caller holds a signed cookie for, preventing unauthorized activation or revocation of other sessions.
Fixed the OIDC /oauth2/endsession endpoint to reject cross-site GET logout requests carrying only a session cookie, while leaving logout authenticated by a valid id_token_hint unaffected.
Fixed WeChat sign-in to succeed without an email address by assigning a stable placeholder, matching the behavior expected from the default configuration.
For detailed changes, see CHANGELOG
@better-auth/api-key
Bug Fixes
Fixed API key updates to fail when the caller's session has been revoked server-side, instead of succeeding within the cookie-cache window (#9991)
Prevented server-only endpoints from being accidentally exposed over HTTP (#9835)
Fixed concurrent API key verification from driving the remaining-uses count below zero or exceeding the rate limit. Secondary-storage-only deployments remain best-effort for these counters.
For detailed changes, see CHANGELOG
@better-auth/sso
Bug Fixes
Fixed SAML replay protection to hold under concurrent requests, preventing a SAML assertion submitted twice simultaneously from being accepted more than once.
Fixed SSO domain verification to allow organization admins and owners to verify domains for providers their organization owns, not just the member who originally registered the provider.
Fixed trustEmailVerified to no longer treat the string "false" as a verified email, accepting only a boolean true or the string "true" as confirmation.
For detailed changes, see CHANGELOG
auth
Bug Fixes
Fixed the CLI to resolve SvelteKit ($app/, $env/), Vite asset imports (?raw, ?url), and Cloudflare Workers (cloudflare:workers) virtual-module imports when loading the auth config (#9834)
Fixed the CLI to skip Unsupported() fields when regenerating the Prisma schema (#10011)
Fixed the CLI to update existing Prisma field types when regenerating the schema, such as correctly emitting BigInt or Int when bigint configuration changes (#9729)
For detailed changes, see CHANGELOG
@better-auth/expo
Bug Fixes
Hardened request trust validation for the Expo authorization proxy, including rejecting redirect and callback targets not in trustedOrigins (#9990)
Fixed Expo social account linking to include the stored session cookie when using an ID token with linkSocial (#9953)
For detailed changes, see CHANGELOG
@better-auth/memory-adapter
Bug Fixes
Fixed the memory adapter to not discard writes from concurrent operations on a failed transaction, made update and delete with an empty filter a no-op instead of affecting all rows, and made updateMany return the number of affected rows.
Fixed counter updates on the memory, Kysely, Drizzle, Prisma, and MongoDB adapters to be atomic by default, ensuring correct rate limiting and API-key usage limit enforcement.
For detailed changes, see CHANGELOG
@better-auth/scim
Bug Fixes
Fixed organization-scoped SCIM deletes to remove members through the organization adapter, ensuring team memberships and member-removal hooks are applied correctly.
Fixed SCIM bearer token comparison to use constant-time evaluation, closing a timing side channel that could help an attacker recover a valid token.
For detailed changes, see CHANGELOG
@better-auth/core
Bug Fixes
Fixed provider identity validation for Google One Tap, Microsoft Entra ID, SSO, WeChat, and Reddit sign-in to enforce tenant restrictions and reject tokens issued for other applications (#10003)
For detailed changes, see CHANGELOG
@better-auth/drizzle-adapter
Bug Fixes
Fixed updateMany to return the number of rows it affected, as the adapter contract specifies.
For detailed changes, see CHANGELOG
@better-auth/electron
Bug Fixes
Fixed a race condition where an Electron authorization code could be exchanged for a session more than once when the exchange was attempted concurrently.
For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
Fixed SQLite mutations through the Bun and Node Kysely drivers to correctly report affected row counts and inserted row IDs, fixed multiple query parameter binding in the Bun driver, and made consumeOne work on SQL Server.
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
Bug Fixes
Fixed token introspection and revocation to cache signing keys per auth instance instead of fetching them from the database on every request.
For detailed changes, see CHANGELOG
@better-auth/passkey
Bug Fixes
Fixed passkey challenge validation to reject registration challenges used for authentication (and vice versa), and to fail when the target user cannot be resolved.
For detailed changes, see CHANGELOG
@better-auth/prisma-adapter
Bug Fixes
Fixed the Prisma adapter to surface delete errors instead of silently reporting success when a deletion fails for any reason other than the record being absent.
For detailed changes, see CHANGELOG
@better-auth/redis-storage
Bug Fixes
Fixed Redis-backed rate-limit windows to set expiry once when the window opens instead of extending it with continued traffic, and added an atomic increment method to Redis secondary storage.
For detailed changes, see CHANGELOG
@better-auth/stripe
Bug Fixes
Fixed several Stripe subscription issues: reused existing customers by email only when verified, synced subscription status from the checkout session on success, scoped cancellation and restoration to the targeted subscription, validated returnUrl against trustedOrigins, and checked all subscriptions on organization deletion (#9971)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@arnnvv, @Bekacru, @bytaesu, @GautamBytes, @gustavovalverde, @SferaDev
Full changelog: v1.6.16...v1.6.17
Original source - Jun 10, 2026
- Date parsed from source:Jun 10, 2026
- First seen by Releasebot:Jun 10, 2026
v1.7.0-beta.5
Better Auth releases a broad beta update with major OAuth and social login changes, including consolidated ID token verification, new Microsoft Entra ID client assertion support, per-provider email verification, SSO provisioning controls, Electron sign-in hardening, and many bug fixes.
better-auth
❗ Breaking Changes
Consolidated id_token verification for social providers into a single shared verifier (#9828)
Migration: Custom providers implementing UpstreamProvider must replace the removed verifyIdToken method with an idToken config object containing jwks, issuer, and audience. For verification without a local JWKS, pass idToken: { verify: async (token, nonce) => boolean }. PayPal no longer supports client-submitted id_token sign-in (signIn.social({ idToken })); the redirect flow is unchanged.
Features
Added clientAssertion support to the Microsoft Entra ID social provider (#9898)
Added per-provider requireEmailVerification option for social sign-in (#9929)
Added id_token verification and client-submitted id_token sign-in for genericOAuth providers configured with a discoveryUrl (#9966)
Bug Fixes
Fixed generated schema to accept null for optional fields (#9841)
Fixed fresh age check in listSessions (#9865)
Fixed admin plugin to return USER_NOT_FOUND when attempting to update a non-existent user (#9875)
Fixed missing Origin/Referer validation on cookieless email sign-in and sign-up (#9973)
Fixed plugin-owned session fields being incorrectly accepted as user input (#9965)
Fixed getSessionCookie to prefer the __Secure- prefixed cookie when available (#9806)
Fixed organization invitation verification to correctly scope email verification requirements: listUserInvitations always requires a verified session, and requireEmailVerificationOnInvitation applies only to by-ID recipient operations (#9877)
Fixed session-delete hooks not firing for preserved sessions when using secondaryStorage (#9969)
Fixed update-session and token routes to respect server-side session deletion (#9967)
Improved cookie parsing regex character ranges for correctness (#9879)
For detailed changes, see CHANGELOG
@better-auth/oauth-provider
❗ Breaking Changes
Added OIDC Back-Channel Logout support, notifying connected apps and revoking access tokens immediately on sign-out (#9304)
Migration: Access tokens whose bound session has ended now return { active: false } at introspection and are rejected at /oauth2/userinfo. Run migrate/generate to add three new schema fields: oauthClient.backchannelLogoutUri, oauthClient.backchannelLogoutSessionRequired, and oauthAccessToken.revoked. On serverless runtimes, configure advanced.backgroundTasks.handler to keep sign-out latency low.
Enforced max_age parameter so users are now prompted to re-authenticate when their session exceeds the requested age (#9936)
Migration: Flows that relied on max_age being silently ignored will now prompt users to log in again. No configuration change is required.
Features
Added support for POST requests on the userinfo endpoint (#9937)
Bug Fixes
Fixed various bugs across packages (#9974)
Fixed private_key_jwt JTI replay prevention to be atomic across processes (#9964)
Fixed JWT access token revocation to return unsupported_token_type instead of a misleading success response (#9970)
Fixed configured hooks not running when an authorization request resumes (#9919)
For detailed changes, see CHANGELOG
@better-auth/sso
❗ Breaking Changes
Hardened the validateUserInfo source contract for SSO providers (#9940)
Migration: In validateUserInfo callbacks, OIDC and SAML SSO sign-ins now pass source.sso (with ssoProviderId and provider claims or assertion attributes) instead of source.oauth. Update any callbacks that inspect provider data for SSO flows to read from source.sso when source.method is 'sso'.
Features
Added user.validateUserInfo provisioning gate to allow rejecting identities before user creation or account linking (#9864)
Added a server-trusted state channel for OAuth redirects and fixed anonymous account linking in cookieless flows (#9930)
Bug Fixes
Fixed SAML AuthnRequest consumption to be atomic (#9972)
Fixed SAML ERR_SUBJECT_UNCONFIRMED errors by passing clockSkew to samlify's clockDrifts (#9748)
For detailed changes, see CHANGELOG
@better-auth/electron
❗ Breaking Changes
Enforced PKCE S256 and hardened custom-scheme origin matching in the Electron sign-in flow (#9645)
Migration: Upgrade both the @better-auth/electron client and server together, and ensure your app's custom scheme is listed in trustedOrigins. The disableOriginOverride option is removed. Custom-scheme entries in trustedOrigins now match by scheme and authority instead of string prefix, so myapp://callback no longer matches myapp://callback.attacker.tld.
For detailed changes, see CHANGELOG
auth
❗ Breaking Changes
Changed OAuth account scopes from a single scope string to an accumulated grantedScopes array, preserving incremental grants across sign-ins (#9825)
Migration: Run migrate/generate to add the grantedScopes column, then backfill from account.scope by splitting on commas and whitespace, trimming and deduping tokens. Encoding varies by adapter: native array on PostgreSQL with Drizzle/Prisma, jsonb with Kysely, JSON string array on MySQL/SQLite/MSSQL. Drop the legacy scope column after verifying. Custom providers must rename OAuthProvider to UpstreamProvider and remove defaultScopes.
For detailed changes, see CHANGELOG
@better-auth/kysely-adapter
Bug Fixes
Fixed migration constant imports to use kysely/migration (#9811)
Fixed Turbopack build failures by inlining migration-table constants (#9933)
For detailed changes, see CHANGELOG
@better-auth/passkey
Features
Added authenticator name resolution from AAGUID at read time (#9927)
For detailed changes, see CHANGELOG
Contributors
Thanks to everyone who contributed to this release:
@Bekacru, @bytaesu, @GautamBytes, @gustavovalverde, @ItalyPaleAle, @ping-maxwell, @seebykilian, @WilsonnnTan, @zeroknowledge0x
Full changelog: v1.7.0-beta.4...v1.7.0-beta.5
Original source
Curated by the Releasebot team
Releasebot is an aggregator of official release notes from hundreds of software vendors and thousands of sources.
Our editorial process involves the manual review and audit of release notes procured with the help of automated systems.