Better Auth

Frameworks & LibrariesMIT

Framework-agnostic authentication and authorization for TypeScript.

Latest v1.7.3 · by Better AuthWritten in TypeScriptWebsitebetter-auth/better-authRSS

Branches

1.7
v1.7.3
1.6
v1.6.30

Release activity

Release activity — 23 releases across 16 days since Jun 20, 2026. Each cell is one day; darker means more releases that day. Nothing is recorded before Jun 20, 2026. Older weeks are hidden at this screen width.
JunJulAugSep
SundayNo releases on Jun 21, 2026No releases on Jun 28, 2026No releases on Jul 5, 2026No releases on Jul 12, 2026No releases on Jul 19, 2026No releases on Jul 26, 2026No releases on Aug 2, 2026No releases on Aug 9, 2026No releases on Aug 16, 2026No releases on Aug 23, 2026No releases on Aug 30, 20261 release on Sep 6, 2026
MondayNo releases on Jun 22, 20261 release on Jun 29, 2026No releases on Jul 6, 2026No releases on Jul 13, 2026No releases on Jul 20, 2026No releases on Jul 27, 20261 release on Aug 3, 2026No releases on Aug 10, 20261 release on Aug 17, 2026No releases on Aug 24, 2026No releases on Aug 31, 2026No releases on Sep 7, 2026
TuesdayNo releases on Jun 23, 2026No releases on Jun 30, 2026No releases on Jul 7, 2026No releases on Jul 14, 2026No releases on Jul 21, 2026No releases on Jul 28, 20261 release on Aug 4, 20262 releases on Aug 11, 20262 releases on Aug 18, 2026No releases on Aug 25, 2026No releases on Sep 1, 2026No releases on Sep 8, 2026
WednesdayNo releases on Jun 24, 2026No releases on Jul 1, 2026No releases on Jul 8, 2026No releases on Jul 15, 20262 releases on Jul 22, 2026No releases on Jul 29, 20261 release on Aug 5, 2026No releases on Aug 12, 2026No releases on Aug 19, 20261 release on Aug 26, 2026No releases on Sep 2, 2026
ThursdayNo releases on Jun 25, 20261 release on Jul 2, 2026No releases on Jul 9, 2026No releases on Jul 16, 20261 release on Jul 23, 2026No releases on Jul 30, 2026No releases on Aug 6, 20261 release on Aug 13, 2026No releases on Aug 20, 2026No releases on Aug 27, 2026No releases on Sep 3, 2026
Friday4 releases on Jun 26, 2026No releases on Jul 3, 2026No releases on Jul 10, 2026No releases on Jul 17, 2026No releases on Jul 24, 2026No releases on Jul 31, 2026No releases on Aug 7, 20262 releases on Aug 14, 2026No releases on Aug 21, 2026No releases on Aug 28, 2026No releases on Sep 4, 2026
Saturday1 release on Jun 20, 2026No releases on Jun 27, 2026No releases on Jul 4, 2026No releases on Jul 11, 2026No releases on Jul 18, 2026No releases on Jul 25, 2026No releases on Aug 1, 2026No releases on Aug 8, 2026No releases on Aug 15, 2026No releases on Aug 22, 2026No releases on Aug 29, 2026No releases on Sep 5, 2026

23 releases since Jun 20, 2026, busiest day 4

Changelog

Filter releases by branch
23 of 23 releases

v1.7.3

Latest
Added 9
  • Add Cloudflare as a built-in social provider with client-secret and PKCE support
  • Add schema validation during initialization by default, including in production, and reject authentication requests on detected mismatches
  • Add isPasswordCompromised for checking passwords against Have I Been Pwned in custom server-side flows
  • Add custom token endpoint authentication strategies for providers with non-standard request parameters
  • Add initialization-time schema validation and actionable mismatch guidance for Drizzle and Prisma adapters
  • Add diagnostics in auth generate for required fields in existing Prisma schemas that Better Auth never writes
Fixed 16
  • Align generated OpenAPI required fields with runtime validation
  • Handle malformed custom-scheme callback URLs without excessive processing
  • Improve Auth0 domain normalization to avoid slow trailing-slash processing
  • Prevent generic OAuth discovery failures from taking down the authentication API
  • Track email OTP sign-ins in the last login method plugin
  • Fix callback hooks being skipped after proxied OAuth sign-ins and preserve server state when callback cookies are unavailable
Deprecated 1
  • The legacy /oauth-proxy-callback endpoint is deprecated and will be removed in the next minor release

From Better Auth

Upgrading from 1.7.0–1.7.2:

We restored the 1.6 account core schema to avoid requiring a disruptive backfill for existing users. We recognize the cost to users who already migrated and are committed to keeping the core schema stable throughout v1.

If you applied the 1.7 issuer schema, follow the upgrade guide for the required cleanup. No backfill is needed.

better-auth
Features
  • Added Cloudflare as a built-in social provider with client-secret and PKCE support. (#9908)
  • Enabled schema validation during initialization by default, including in production, and rejected authentication requests on detected mismatches. (#11178)
  • Added isPasswordCompromised for checking passwords against Have I Been Pwned in custom server-side flows. (#11147)
Bug Fixes
  • Aligned generated OpenAPI required fields with runtime validation. (#11066)
  • Handled malformed custom-scheme callback URLs without excessive processing. (#11060)
  • Improved Auth0 domain normalization to avoid slow trailing-slash processing. (#11188)
  • Prevented generic OAuth discovery failures from taking down the authentication API. (#10978)
  • Tracked email OTP sign-ins in the last login method plugin. (#10963)
  • Fixed callback hooks being skipped after proxied OAuth sign-ins and preserved server state when callback cookies are unavailable. The legacy /oauth-proxy-callback endpoint is deprecated and will be removed in the next minor release. (#10988)
  • Fixed getSession failures when cookie caching is disabled and cached session cookies remain. (#11120)
  • Fixed TOTP re-enrollment replacing an active authenticator and its backup codes. (#11037)
  • Prevented duplicate session requests and hydration mismatches in Nuxt useFetch. (#11084)
  • Improved dynamic organization role permission check performance. (#11069)
  • Added type-safe Nuxt useFetch integration for the Vue client's useSession hook. (#11085)
  • Restored compatibility with 1.6 account schemas by identifying accounts with (providerId, accountId) instead of issuer. (#11153)

For detailed changes, see CHANGELOG

@better-auth/core
Features
  • Added Cloudflare as a built-in social provider with client-secret and PKCE support. (#9908)
  • Added initialization-time schema validation and actionable mismatch guidance for Drizzle and Prisma adapters. (#11179)
  • Enabled schema validation during initialization by default, including in production, and rejected authentication requests on detected mismatches. (#11178)
  • Added custom token endpoint authentication strategies for providers with non-standard request parameters. (#11101)
Bug Fixes
  • Restored optional consumeOne and incrementOne methods for custom database adapters. (#11189)
  • Fixed TikTok sign-in and token refresh with the documented clientKey and clientSecret options. (#11102)
  • Improved request IP validation performance. (#11068)
  • Improved PayPal authorization code and refresh token requests, including PKCE handling. (#11129)
  • Improved Reddit token requests with OAuth-compliant Basic authentication and redirect protection. (#11134)
  • Restored compatibility with 1.6 account schemas by identifying accounts with (providerId, accountId) instead of issuer. (#11153)

For detailed changes, see CHANGELOG

auth
Features
  • Added Cloudflare as a built-in social provider with client-secret and PKCE support. (#9908)
  • Added diagnostics in auth generate for required fields in existing Prisma schemas that Better Auth never writes. (#11179)
  • Added Codex as a supported target for configuring the Better Auth documentation MCP server. (#11100)
  • Added schema diagnostics to auth migrate and auth generate, blocking migrations when required columns that Better Auth never writes need manual repair. (#11178)
Bug Fixes
  • Updated auth info to report installed dependency versions instead of declared specifiers. (#11126)
  • Allowed auth init to complete when generated setup groups have no dependencies. (#11140)
  • Skipped unsupported dependency specifiers during auth upgrade with a clear warning. (#11127)

For detailed changes, see CHANGELOG

@better-auth/drizzle-adapter
Features
  • Added initialization-time schema validation and actionable mismatch guidance for Drizzle and Prisma adapters. (#11179)

For detailed changes, see CHANGELOG

@better-auth/test-utils
Bug Fixes
  • Improved adapter test suite performance by avoiding redundant cleanup queries. (#10762)
  • Updated adapter test suites to remove issuer-specific account requirements. (#11153)

For detailed changes, see CHANGELOG

@better-auth/cimd
Bug Fixes
  • Fixed CIMD client metadata discovery failures with ERR_INVALID_IP_ADDRESS on supported Node.js versions. (#10730)

For detailed changes, see CHANGELOG

@better-auth/expo
Bug Fixes
  • Prevented Expo apps from sending unauthenticated requests during interrupted cookie storage updates. (#11099)

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Features
  • Enabled database schema validation by default and rejected authentication requests when the live schema does not match. (#11178)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
Bug Fixes
  • Allowed native OAuth clients using localhost loopback redirects to use ephemeral callback ports. (#11090)

For detailed changes, see CHANGELOG

@better-auth/prisma-adapter
Features
  • Added initialization-time schema validation and actionable mismatch guidance for Drizzle and Prisma adapters. (#11179)

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Restored compatibility with 1.6 account schemas by identifying accounts with (providerId, accountId) instead of issuer. (#11153)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@BetterAndBetterII, @bytaesu, @erikpr1994, @gustavovalverde, @harshil1712, @onmax, @Salman-Arshad, @starslingdev[bot], @supercell02, @thisismert

Full changelog: v1.7.2...v1.7.3

View originalPermalink
How v1.7.3 went

v1.7.2

Added 3
  • Added warnings for invalid signed session data in the cookie cache
  • Added synchronous and optional access to the current auth endpoint context
  • Added validation for missing Drizzle schema fields in compound where clauses
Changed 2
  • Improved getTestInstance performance with a faster default password hasher
  • Standardized built-in placeholder emails to the namespaced {identifier}@{namespace}.placeholder.invalid format
Fixed 12
  • Fixed permanent user bans to clear expiration dates from previous temporary bans
  • Fixed client types with more plugins being assignable to types declaring fewer plugins
  • Fixed disabled MyISAM indexes from satisfying migration index checks
  • Fixed programmatic migrations on Cloudflare D1 while preserving existing-index validation
  • Allowed ~ in relative callback URLs validated by trusted-origin checks
  • Improved validation of relative callback and redirect URLs with paths, queries, and fragments

From Better Auth

better-auth
Bug Fixes
  • Fixed permanent user bans to clear expiration dates from previous temporary bans. (#10823)
  • Fixed client types with more plugins being assignable to types declaring fewer plugins. (#10907)
  • Added warnings for invalid signed session data in the cookie cache. (#10934)
  • Fixed disabled MyISAM indexes from satisfying migration index checks. (#10877)
  • Fixed programmatic migrations on Cloudflare D1 while preserving existing-index validation. (#10875)
  • Allowed ~ in relative callback URLs validated by trusted-origin checks. (#10041)
  • Improved validation of relative callback and redirect URLs with paths, queries, and fragments. (#10979)
  • Allowed same-origin form submissions with Referrer-Policy: no-referrer while continuing to reject untrusted origins. (#10959)
  • Improved getTestInstance performance with a faster default password hasher. (#10879)
  • Standardized built-in placeholder emails to the namespaced {identifier}@{namespace}.placeholder.invalid format. (#10982)

For detailed changes, see CHANGELOG

@better-auth/core
Bug Fixes
  • Fixed async context loss in Cloudflare Workers bundles with multiple runtime conditions. (#10855)
  • Fixed auth request logs to respect the configured logger, log level, and disabled setting. (#10939)
  • Improved validation of relative callback and redirect URLs with paths, queries, and fragments. (#10979)
  • Standardized built-in placeholder emails to the namespaced {identifier}@{namespace}.placeholder.invalid format. (#10982)
  • Added synchronous and optional access to the current auth endpoint context. (#10938)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
Bug Fixes
  • Fixed Client ID Metadata Document registration when clients share at least one supported grant with the server. (#11010)
  • Improved validation of relative callback and redirect URLs with paths, queries, and fragments. (#10979)
  • Fixed relative redirect URLs containing fragments. (#10983)

For detailed changes, see CHANGELOG

@better-auth/drizzle-adapter
Bug Fixes
  • Fixed one-to-one Drizzle relations when usePlural is enabled. (#10941)
  • Added validation for missing Drizzle schema fields in compound where clauses. (#10859)

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Bug Fixes
  • Fixed programmatic migrations on Cloudflare D1 while preserving existing-index validation. (#10875)

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Improved validation of relative callback and redirect URLs with paths, queries, and fragments. (#10979)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu, @GautamBytes, @heliohm, @sosyz, @starslingdev[bot]

Full changelog: v1.7.1...v1.7.2

View originalPermalink
How v1.7.2 went

v1.7.1

Added 1
  • Add optional SCIM-owned connection and credential catalog via managedConnections configuration for creating runtime tenant connections and managing bearer credentials
Changed 3
  • Update bundled dependencies (jose, nanostores, noble crypto packages, SimpleWebAuthn) to their latest compatible releases
  • Enforce signing policy and size limits on SP metadata
  • Make wantAssertionsSigned now correctly control whether the SP requires signed assertions
Fixed 11
  • Add native database transaction support to test instances for PostgreSQL and MySQL
  • Fix case-insensitive parsing of string Boolean values for SCIM User active and primary sub-attributes of emails, phoneNumbers, addresses, roles, and entitlements at the HTTP ingress
  • Fix an issue where trusted server code could not retain a terminal connection binding before a dynamic SCIM connection's first authenticated request when supplying a provisioning domain during decommissioning
  • Fix SSO provider registration to allow reusing a SCIM connection ID
  • Fix SAML assertion signature verification to validate signatures on the raw assertion instead of trusting an already-parsed response
  • Fix Client ID Metadata Document caching to follow shared-cache freshness rules with proper handling of s-maxage, max-age, Expires, ETag, and Last-Modified

From Better Auth

better-auth
Bug Fixes
  • Added native database transaction support to test instances for PostgreSQL and MySQL.
  • Updated bundled dependencies (jose, nanostores, noble crypto packages, SimpleWebAuthn) to their latest compatible releases, with no changes required to existing projects.

For detailed changes, see CHANGELOG

@better-auth/scim
Bug Fixes
  • Fixed case-insensitive parsing of string Boolean values for SCIM User active and the primary sub-attribute of emails, phoneNumbers, addresses, roles, and entitlements at the HTTP ingress, improving Microsoft Entra interoperability.
  • Added an optional SCIM-owned connection and credential catalog: configure managedConnections to allow trusted server code to create runtime tenant connections and issue, rotate, and revoke bearer credentials through server-only auth.api methods, without a code-defined connection or an application-owned verifier.
  • Fixed an issue where trusted server code could not retain a terminal connection binding before a dynamic SCIM connection's first authenticated request when supplying a provisioning domain during decommissioning.

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Fixed SSO provider registration to allow reusing a SCIM connection ID, as SCIM connections no longer participate in the authentication provider namespace.
  • Fixed SAML assertion signature verification to validate signatures on the raw assertion instead of trusting an already-parsed response, and enforced signing policy and size limits on SP metadata. wantAssertionsSigned now correctly controls whether the SP requires signed assertions, matching real-world IdP signing behavior.

For detailed changes, see CHANGELOG

@better-auth/cimd
Bug Fixes
  • Fixed Client ID Metadata Document caching to follow shared-cache freshness rules: the plugin now prefers s-maxage over max-age and Expires, honors s-maxage=0, conditionally revalidates with ETag or Last-Modified, and treats invalid or duplicate freshness directives as immediately stale. Concurrent refreshes now converge on a single client-resource link instead of failing on a unique constraint.

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Bug Fixes
  • Fixed native adapter transactions for raw database instances (better-sqlite3, node:sqlite, bun:sqlite, mysql2, pg) passed directly as database, matching the behavior of the explicit { db }/{ dialect } config shapes. Plugins requiring native transactions (such as @better-auth/scim) now work correctly when using the quickstart database: new Database(...) form.

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
Bug Fixes
  • Fixed scope error responses so MCP clients now receive a 403 with an RFC 6750 insufficient_scope WWW-Authenticate challenge naming every missing scope, allowing clients to request all needed scopes in a single authorization request.

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed the CLI to refuse adding required columns without default values to already-populated tables (#10863)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@gustavovalverde

Full changelog: v1.7.0...v1.7.1

View originalPermalink
How v1.7.1 went

v1.7.0

Added 3
  • Added OIDC back-channel logout so ending a session cuts off every connected app's API access
  • Added OTP-only two-factor enablement with a discriminated enableTwoFactor response
  • Added unique lookup indexes for the device authorization deviceCode and userCode columns
Changed 10
  • Moved database joins from experimental into the stable advanced.database.joins option
  • Scoped account identity by trusted issuer, keying accounts on (issuer, accountId)
  • Required captcha endpoint entries to match full auth paths with wildcard support
  • Modeled OAuth protected resources explicitly with per-resource TTLs, scopes, claims, and signing pins
  • Decoupled SCIM provisioning from the organization plugin
  • Resolved the auth origin from Host by default when using a dynamic baseURL
Removed 2
  • Moved the MCP plugin into its own @better-auth/mcp package
  • Removed the deprecated oidcProvider plugin

From Better Auth

Blog post: Better Auth 1.7

better-auth
❗ Breaking Changes
  • Moved database joins out of experimental into the stable advanced.database.joins option (#10359)

    Migration: Replace experimental: { joins: true } with advanced: { database: { joins: true } }. Drizzle and Prisma users should regenerate their schema (npx auth@latest generate) so it includes the required relations.

  • Scoped account identity by trusted issuer, keying accounts on (issuer, accountId) (#10403)

    Migration: Accounts now require Account.issuer. Read provider identity from accountInfo.account.accountId, drop mapping.id from SSO configs, and give the microsoftEntraId helper a concrete tenant GUID. Apply the account-identity backfill in the 1.7 upgrade guide before deploying.

  • Required captcha endpoint entries to match full auth paths, with wildcard support (#10004)

    Migration: Replace partial paths such as /sign-in with explicit wildcards like /sign-in/* or /sign-in/**.

  • Moved the MCP plugin into its own @better-auth/mcp package built on the OAuth provider (#9992)

    Migration: Install @better-auth/mcp and @better-auth/cimd, add the now-required jwt() plugin, and move options nested under oidcConfig to flat mcp({ ... }) options. Rename withMcpAuth to requireMcpAuth and mcpHandler to createMcpProtectedRequestHandler. Regenerate the schema (npx auth migrate): oauthApplication becomes oauthClient, plus new oauthRefreshToken and oauthClientAssertion tables.

  • Added OIDC back-channel logout so ending a session cuts off every connected app's API access (#9304)

    Migration: Introspecting an access token whose session has ended now returns { active: false }, and /oauth2/userinfo rejects it. Clients opt into notifications by registering backchannel_logout_uri. Run the schema migration for the new oauthClient and oauthAccessToken columns.

  • Modeled OAuth protected resources explicitly, with per-resource TTLs, scopes, claims, and signing pins (#9648)

    Migration: validAudiences is removed: move each resource identifier into resources and link restricted clients through oauthClientResource. @better-auth/mcp now requires an explicit resource. Run npx @better-auth/cli generate and apply the migration before deploying.

  • Decoupled SCIM provisioning from the organization plugin (#10390)

    Migration: SCIM configuration, client APIs, database schema, and the Group model are all replaced, and provisioning state cannot migrate in place. Follow the SCIM cutover in the 1.7 upgrade guide, including a full directory reprovision, before resuming traffic.

  • Added OTP-only two-factor enablement with a discriminated enableTwoFactor response (#9057)

    Migration: enableTwoFactor now returns a method field ("otp" or "totp"); narrow on it before reading totpURI and backupCodes. Pass method: "otp" for OTP enrollment, which requires otpOptions.sendOTP.

  • Resolved the auth origin from Host by default when using a dynamic baseURL (#9134)

    Migration: If your proxy exposes the public hostname only through x-forwarded-host, set advanced.trustedProxyHeaders: true. Deployments where the proxy rewrites Host (nginx default, Vercel, Cloudflare, Netlify) are unaffected.

  • Added unique lookup indexes for the device authorization deviceCode and userCode columns (#10059)

    Migration: Resolve duplicate code values before applying the migration. MySQL and SQL Server installations must also convert both columns to bounded strings and clean up values longer than 191 characters.

  • Enforced S256 PKCE in the Electron sign-in flow and hardened custom-scheme origin checks (#9645)

    Migration: Upgrade the @better-auth/electron client and server together and add your app's scheme to trustedOrigins. The code_challenge_method parameter and disableOriginOverride option are removed, and host-bearing custom-scheme entries now match that host exactly.

  • Identified Microsoft Entra accounts by the stable oid claim (#10204)

    Migration: Migrate existing Microsoft account rows created from sub before upgrading. Tokens without a valid oid are rejected.

  • Required a Google client ID before Google One Tap verifies ID tokens (#10036)

    Migration: Configure oneTap({ clientId }) or socialProviders.google.clientId.

  • Removed the deprecated oidcProvider plugin (#10031)

    Migration: Move OIDC authorization-server integrations to @better-auth/oauth-provider.

  • Rewrote the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults (#9069)

    Migration: Replace signIn.oauth2({ providerId }) with signIn.social({ provider }), oauth2.link() with linkSocial(), and drop genericOAuthClient(). Callbacks move to /api/auth/callback/:id, pkce now defaults to true, and issuer and requireIssuerValidation are removed in favor of OIDC discovery.

  • Separated OAuth device grant ownership into oauthDeviceAuthorization() (#10746)

    Migration: The OAuth integration replaces the optional resource column with oauthClientId and resources, so regenerate and apply the schema. Let pending device codes expire before upgrading from an earlier 1.7 prerelease.

  • Verified provider id_tokens with a single shared verifier (#9828)

    Migration: Custom UpstreamProvider implementations replace the removed verifyIdToken method with an idToken config carrying a JWKS source, issuer, and audience. PayPal client id_token sign-in now returns ID_TOKEN_NOT_SUPPORTED; its redirect flow is unchanged.

Features
  • Added clientAssertion support to the Microsoft Entra ID social provider (#9898)
  • Made the Auth instance directly fetchable (#9431)
  • Added per-provider requireEmailVerification for social sign-in (#9929)
  • Added a user.validateUserInfo gate for rejecting an identity before a user is created or linked (#9864)
  • Added hydrateSession so useSession returns server-fetched data on the first render (#8733)
  • Added compound table indexes to plugin database schemas (#10402)
  • Added allowIdpInitiated support for IdP-initiated flows through a secure server-side bounce (#9301)
  • Added RP-initiated logout so signOut() can also sign users out of the OpenID provider (#9368)
  • Added refreshTokenParams for forwarding extra parameters on generic OAuth token refresh (#9948)
  • Verified discovery id_tokens against the provider JWKS and enabled id_token sign-in for generic OAuth (#9966)
  • Added the OAuth device authorization grant (RFC 8628) (#10135)
  • Added DPoP sender-constrained access tokens (RFC 9449) (#10039)
  • Added the at_hash claim to ID tokens issued alongside an access token, per OIDC Core §3.1.3.6 (#9079)
  • Added private_key_jwt client authentication for token endpoint requests (RFC 7523) (#8836)
  • Sent Cache-Control: no-store on every OAuth response that carries credentials (#10065)
  • Added per-request additionalParams and loginHint across signIn.social, linkSocial, and signIn.sso (#9305)
  • Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers (#9930)
  • Allowed passing userId and organizationId to the listUserTeams API (#8977)
  • Added organization.getOrganization() for metadata-only fetches (#10397)
  • Added a server-only consumePhoneNumberOTP API for custom phone OTP flows (#9766)
  • Added JWKS-backed asymmetric JWTs for the session cookie cache (#8931)
  • Added transactional OIDC user resolution for SSO sign-ins (#10473)
  • Added an immutable username option (#9240)
  • Allowed omitting the username plugin's separate displayUsername field (#10330)
Bug Fixes
  • Allowed test instances to enable native database transactions for PostgreSQL and MySQL.
  • Refreshed bundled dependencies (jose, nanostores, the noble crypto packages, and SimpleWebAuthn) to their latest compatible releases. These updates are backward compatible and require no changes to existing projects.
  • Widened the drizzle-kit peer dependency range (#10299)
  • Decoupled the session cookie cache from JWT plugin internals (#10666)
  • Allowed auth migrate to add required columns with static defaults and nullable unique columns to existing tables (#10293)
  • Bound the ID token nonce to the authorization request in the generic OAuth redirect flow (#10095)
  • Fixed a sign-up deadlock when JWT cookie caching ran on a single-connection SQLite database with native transactions (#10622)
  • Created new OAuth accounts inside the user creation transaction (#10125)
  • Derived the OAuth redirect_uri from the per-request base URL in multi-host deployments (#10127)
  • Preserved previously granted account.scope values across re-authentication and token refresh (#10128)
  • Preserved the resolved OAuth user when overrideUserInfo returns null (#10124)
  • Fired session-delete hooks and revoked bound OAuth tokens for preserved sessions on secondaryStorage (#9969)
  • Issued SIWE nonces before the wallet address and chain ID are known (#10234)
  • Fixed client plugin composition so One Tap, Electron, and Expo type-check with createAuthClient (#10505)
  • Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication (#9657)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
❗ Breaking Changes
  • Moved the MCP plugin into its own @better-auth/mcp package built on the OAuth provider (#9992)

    Migration: Install @better-auth/mcp and @better-auth/cimd, add the now-required jwt() plugin, and move options nested under oidcConfig to flat mcp({ ... }) options. Rename withMcpAuth to requireMcpAuth and mcpHandler to createMcpProtectedRequestHandler. Regenerate the schema (npx auth migrate): oauthApplication becomes oauthClient, plus new oauthRefreshToken and oauthClientAssertion tables.

  • Added OIDC back-channel logout so ending a session cuts off every connected app's API access (#9304)

    Migration: Introspecting an access token whose session has ended now returns { active: false }, and /oauth2/userinfo rejects it. Clients opt into notifications by registering backchannel_logout_uri. Run the schema migration for the new oauthClient and oauthAccessToken columns.

  • Aligned OAuth client registration and metadata with MCP authorization 2026-07-28 (#10577)

    Migration: Add applicationType and nullable clientDiscoveryId columns, deduplicate existing (clientId, resourceId) links before the new compound unique index, then drop the legacy type and public columns. Replace clientCredentialGrantDefaultScopes with per-client clientCredentialsScopes, backfilling every client to [] and reassigning approved machine scopes after an audit. mcp() no longer enables unauthenticated DCR: compose it with cimd() or enable both DCR flags explicitly.

  • Enforced the max_age authorization request parameter (#9936)

    Migration: Users who authenticated longer ago than the requested max_age are now sent back to log in, and the ID token's auth_time reflects the fresh login. Flows that relied on max_age being ignored will prompt again.

  • Made ID token claim authority explicit, reserving OIDC protocol claims for the provider (#10140)

    Migration: customIdTokenClaims, extension claims, and per-issuance idTokenClaims can no longer set protocol claims such as issuer, subject, audience, nonce, auth_time, acr, amr, or azp; use namespaced custom claims instead. ID tokens now carry acr: "0" and discovery advertises only "0".

  • Modeled OAuth protected resources explicitly, with per-resource TTLs, scopes, claims, and signing pins (#9648)

    Migration: validAudiences is removed: move each resource identifier into resources and link restricted clients through oauthClientResource. @better-auth/mcp now requires an explicit resource. Run npx @better-auth/cli generate and apply the migration before deploying.

  • Bound OAuth client authentication to the grant being issued (#10063)

    Migration: Remove grantType from provider.authenticateClient(...), and return { clientId, confirmation? } from a custom OAuthClientAuthenticationStrategy.authenticate instead of a client record.

  • Bound RFC 8707 resource indicators to the authorization grant (#9836)

    Migration: Token and refresh requests may only narrow the authorization's resource; a broader request returns invalid_target. customAccessTokenClaims now receives a resources array in place of the resource string. Run the schema migration to add the new resource columns.

  • Returned RFC-compliant OAuth error envelopes from validation failures (#9277)

    Migration: Authorization errors now redirect to a registered client's trusted redirect URI with state and iss instead of rendering the server error page, and confidential clients must use their registered token_endpoint_auth_method.

  • Rewrote the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults (#9069)

    Migration: Replace signIn.oauth2({ providerId }) with signIn.social({ provider }), oauth2.link() with linkSocial(), and drop genericOAuthClient(). Callbacks move to /api/auth/callback/:id, pkce now defaults to true, and issuer and requireIssuerValidation are removed in favor of OIDC discovery.

  • Separated OAuth device grant ownership into oauthDeviceAuthorization() (#10746)

    Migration: The OAuth integration replaces the optional resource column with oauthClientId and resources, so regenerate and apply the schema. Let pending device codes expire before upgrading from an earlier 1.7 prerelease.

Features
  • Added token endpoint client authentication configuration across the OAuth stack (#9625)
  • Added the @better-auth/cimd Client ID Metadata Document plugin (#9159)
  • Added the OAuth device authorization grant (RFC 8628) (#10135)
  • Added DPoP sender-constrained access tokens (RFC 9449) (#10039)
  • Added an extension surface for registering grants, client authentication methods, discovery metadata, and claim contributors (#10030)
  • Added a refresh token reuse interval that replays the same response for duplicate refresh requests (#10145)
  • Allowed confidential DCR clients to complete authorization-code flows without PKCE (#10146)
  • Added the at_hash claim to ID tokens issued alongside an access token, per OIDC Core §3.1.3.6 (#9079)
  • Made token introspection consistent across opaque and JWT tokens and scoped it to the audience (#10045)
  • Exposed the issuing sessionId to id_token claim contributors (#10113)
  • Honored requested UserInfo claims through a claim registry (#10156)
  • Removed the silenceWarnings option and the well-known endpoint warnings it suppressed (#10703)
  • Added protected dynamic client registration using RFC 7591 initial access tokens (#10037)
  • Added private_key_jwt client authentication for token endpoint requests (RFC 7523) (#8836)
  • Sent Cache-Control: no-store on every OAuth response that carries credentials (#10065)
  • Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers (#9930)
Bug Fixes
  • Enforced RFC 8628 request cardinality and client authentication on the device flow endpoints (#10752)
  • Accepted the OpenID Provider issuer as the aud of private_key_jwt client assertions (#10811)
  • Accepted UserInfo bearer tokens sent in a form-encoded request body (#10155)
  • Allowed nonce-bound confidential clients to request offline_access without PKCE (#10153)
  • Returned a 401 invalid_token challenge from /oauth2/userinfo for invalid tokens (#10068)
  • Completed the RP-initiated logout flow with form-encoded POST support and confirmation pages (#10812)
  • Deferred token revocation and back-channel logout delivery until the session deletion commits (#10472)
  • Accepted form-encoded POST authorization requests and rejected unsupported OIDC request objects (#10151)
  • Handled voluntary and essential acr claim requests per OIDC Core (#10790)
  • Kept profile and email scope claims on the UserInfo response instead of ID tokens (#10152)
  • Made the private_key_jwt jti single-use atomic across processes (#9964)
  • Made redirect_uri conditional at the token endpoint, required only when the authorization included one (#10159)
  • Preserved client key metadata and the requested authentication method during dynamic client registration (#10144)
  • Redirected authorization requests missing response_type to the verified client redirect URI (#10149)
  • Rejected authorization code replay with invalid_grant and revoked tokens issued from that code (#10150)
  • Reported unsupported_token_type when revoking a JWT access token (#9970)
  • Required the openid scope for authorization requests that use the claims parameter (#10791)
  • Returned invalid_grant when a client presents a refresh token issued to another client (#10154)
  • MCP clients that hit a scope wall now learn exactly which scopes to ask for: missing protected scopes produce a 403 with an RFC 6750 insufficient_scope challenge naming every one of them, so clients can request them in a single authorization redirect.
  • Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication (#9657)

For detailed changes, see CHANGELOG

@better-auth/core
❗ Breaking Changes
  • Moved database joins out of experimental into the stable advanced.database.joins option (#10359)

    Migration: Replace experimental: { joins: true } with advanced: { database: { joins: true } }. Drizzle and Prisma users should regenerate their schema (npx auth@latest generate) so it includes the required relations.

  • Scoped account identity by trusted issuer, keying accounts on (issuer, accountId) (#10403)

    Migration: Accounts now require Account.issuer. Read provider identity from accountInfo.account.accountId, drop mapping.id from SSO configs, and give the microsoftEntraId helper a concrete tenant GUID. Apply the account-identity backfill in the 1.7 upgrade guide before deploying.

  • Decoupled SCIM provisioning from the organization plugin (#10390)

    Migration: SCIM configuration, client APIs, database schema, and the Group model are all replaced, and provisioning state cannot migrate in place. Follow the SCIM cutover in the 1.7 upgrade guide, including a full directory reprovision, before resuming traffic.

  • Identified Microsoft Entra accounts by the stable oid claim (#10204)

    Migration: Migrate existing Microsoft account rows created from sub before upgrading. Tokens without a valid oid are rejected.

  • Verified provider id_tokens with a single shared verifier (#9828)

    Migration: Custom UpstreamProvider implementations replace the removed verifyIdToken method with an idToken config carrying a JWKS source, issuer, and audience. PayPal client id_token sign-in now returns ID_TOKEN_NOT_SUPPORTED; its redirect flow is unchanged.

Features
  • Added clientAssertion support to the Microsoft Entra ID social provider (#9898)
  • Added per-provider requireEmailVerification for social sign-in (#9929)
  • Added a user.validateUserInfo gate for rejecting an identity before a user is created or linked (#9864)
  • Added compound table indexes to plugin database schemas (#10402)
  • Added allowIdpInitiated support for IdP-initiated flows through a secure server-side bounce (#9301)
  • Added RP-initiated logout so signOut() can also sign users out of the OpenID provider (#9368)
  • Added refreshTokenParams for forwarding extra parameters on generic OAuth token refresh (#9948)
  • Added an includeGrantedScopes option to the Google provider (#10129)
  • Added DPoP sender-constrained access tokens (RFC 9449) (#10039)
  • Added private_key_jwt client authentication for token endpoint requests (RFC 7523) (#8836)
  • Sent Cache-Control: no-store on every OAuth response that carries credentials (#10065)
  • Added per-request additionalParams and loginHint across signIn.social, linkSocial, and signIn.sso (#9305)
  • Added JWKS-backed asymmetric JWTs for the session cookie cache (#8931)
  • Added transactional OIDC user resolution for SSO sign-ins (#10473)
Bug Fixes
  • Routed CIMD client_id SSRF checks through the shared host classifier, which now rejects IPv4-compatible IPv6, the 6to4 relay prefix, and site-local addresses (#10126)
  • Derived the OAuth redirect_uri from the per-request base URL in multi-host deployments (#10127)
  • Preserved previously granted account.scope values across re-authentication and token refresh (#10128)
  • Fixed client plugin composition so One Tap, Electron, and Expo type-check with createAuthClient (#10505)
  • Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication (#9657)

For detailed changes, see CHANGELOG

@better-auth/sso
❗ Breaking Changes
  • Scoped account identity by trusted issuer, keying accounts on (issuer, accountId) (#10403)

    Migration: Accounts now require Account.issuer. SSO subjects are protocol-defined (sub for OIDC, signed NameID for SAML) and mapping.id is removed; a manual SAML config without metadata XML must set idpMetadata.entityID. Apply the account-identity backfill in the 1.7 upgrade guide before deploying.

  • Added rolling SAML certificate rotation by accepting an array of IdP signing certificates (#8805)

    Migration: getSSOProvider, listSSOProviders, and updateSSOProvider now always return samlConfig.certificate as an array, so drop any Array.isArray branching. Registration rejects SAML configs with no signing-cert source with CERT_SOURCE_MISSING.

  • Hardened the validateUserInfo source contract so it cannot be bypassed or spoofed (#9940)

    Migration: createUser now fails closed when validateUserInfo is configured but no endpoint context or provisioning source is available. Read SSO metadata from source.sso instead of source.oauth, and handle the source.method values sso-oidc and sso-saml.

  • Hardened SAML response validation for InResponseTo, audience restriction, and SessionIndex (#9055)

    Migration: allowIdpInitiated now defaults to false. Set saml.allowIdpInitiated: true to keep accepting unsolicited SAML responses.

  • Consolidated the SAML ACS endpoint, made spMetadata optional, and fixed Single Logout (#9117)

    Migration: Point your IdP's ACS URL at /sso/saml2/sp/acs/:providerId; /sso/saml2/callback/:providerId is removed. callbackUrl is now the post-auth redirect only, and the unused decryptionPvk, additionalParams, idpMetadata.entityURL, and idpMetadata.redirectURL fields are gone.

Features
  • Added a user.validateUserInfo gate for rejecting an identity before a user is created or linked (#9864)
  • Added allowIdpInitiated support for IdP-initiated flows through a secure server-side bounce (#9301)
  • Added private_key_jwt client authentication for token endpoint requests (RFC 7523) (#8836)
  • Added per-request additionalParams and loginHint across signIn.social, linkSocial, and signIn.sso (#9305)
  • Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers (#9930)
  • Added transactional OIDC user resolution for SSO sign-ins (#10473)
  • Extended resolveUser to SAML sign-ins and hardened the provider lifecycle (#10621)
  • Added additionalFields support on ssoProvider (#9445)
Bug Fixes
  • Allowed an SSO provider registration to reuse a SCIM connection ID, since SCIM connections no longer share the authentication provider namespace.
  • Rejected redirecting OIDC discovery, token, userinfo, and JWKS endpoints so SSO works on Cloudflare Workers (#10072)
  • Updated samlify to 2.13.1 for a signed-assertion XML injection fix (#9821)
  • Upgraded samlify to 2.12.0 with XPath injection and XXE fixes (#9121)
  • Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication (#9657)
  • Verified SAML assertion signatures directly instead of trusting an already-parsed response, and applied the same signing policy and size limit to SP metadata as to IdP metadata. wantAssertionsSigned now controls whether signed assertions are required rather than signed response messages, matching how IdPs sign SAML responses in practice.

For detailed changes, see CHANGELOG

@better-auth/scim
❗ Breaking Changes
  • Decoupled SCIM provisioning from the organization plugin (#10390)

    Migration: SCIM configuration, client APIs, database schema, and the Group model are all replaced, and provisioning state cannot migrate in place. Follow the SCIM cutover in the 1.7 upgrade guide, including a full directory reprovision, before resuming traffic.

  • Isolated SCIM provider connections from the organization and SSO plugins (#10249)

    Migration: Define connections statically, resolve them with authentication.verifyBearerToken, or use the optional managedConnections catalog, and connect SCIM resources to users and roles through identity and projection callbacks. Legacy SCIM state is not migrated: back it up, issue new credentials, and fully reprovision Users and Groups.

  • Bound personal SCIM connections to their creator and removed user-session connection management (#9840)

    Migration: The legacy connection management endpoints and providerOwnership are gone, so authorize SCIM administration in your own application. Legacy scimProvider rows and credentials are not migrated: follow the 1.7 SCIM upgrade guide, issue new credentials, and reprovision Users and Groups.

Features
  • Added a user.validateUserInfo gate for rejecting an identity before a user is created or linked (#9864)
  • Added durable SCIM Group resources with connection-scoped membership and lifecycle endpoints (#10018)
  • Added the SCIM Enterprise User extension and the standard User attributes for interop conformance (#10620)
  • Added a managed connection catalog and runtime connection resolution (#10592)
  • Added acquireActiveSCIMUserLink for transaction-safe authentication of provisioned users (#10474)
Bug Fixes
  • Accepted exact case-insensitive string boolean values for SCIM User active and the primary sub-attribute of emails, phoneNumbers, addresses, roles, and entitlements at the HTTP ingress, for Microsoft Entra interoperability.
  • Added an optional SCIM-owned connection and credential catalog. Configure managedConnections so trusted server code can create runtime tenant connections and issue, rotate, and revoke their bearer credentials through server-only auth.api methods, without a code-defined connection or an application-owned verifier.
  • Allowed trusted server code to retain a terminal connection binding before a dynamic SCIM connection's first authenticated request by supplying its provisioning domain during decommissioning.
  • Created filtered SCIM PATCH values when no target matches instead of rejecting the request (#10682)

For detailed changes, see CHANGELOG

@better-auth/mcp
❗ Breaking Changes
  • Moved the MCP plugin into its own @better-auth/mcp package built on the OAuth provider (#9992)

    Migration: Install @better-auth/mcp and @better-auth/cimd, add the now-required jwt() plugin, and move options nested under oidcConfig to flat mcp({ ... }) options. Rename withMcpAuth to requireMcpAuth and mcpHandler to createMcpProtectedRequestHandler. Regenerate the schema (npx auth migrate): oauthApplication becomes oauthClient, plus new oauthRefreshToken and oauthClientAssertion tables.

  • Aligned OAuth client registration and metadata with MCP authorization 2026-07-28 (#10577)

    Migration: Add applicationType and nullable clientDiscoveryId columns, deduplicate existing (clientId, resourceId) links before the new compound unique index, then drop the legacy type and public columns. Replace clientCredentialGrantDefaultScopes with per-client clientCredentialsScopes, backfilling every client to [] and reassigning approved machine scopes after an audit. mcp() no longer enables unauthenticated DCR: compose it with cimd() or enable both DCR flags explicitly.

  • Modeled OAuth protected resources explicitly, with per-resource TTLs, scopes, claims, and signing pins (#9648)

    Migration: mcp() now requires an explicit resource identifier, for example resource: "https://api.example.com/mcp". validAudiences is removed: move each resource identifier into resources. Run npx @better-auth/cli generate and apply the migration before deploying.

Features
  • Added DPoP sender-constrained access tokens (RFC 9449) (#10039)
  • Added a refresh token reuse interval that replays the same response for duplicate refresh requests (#10145)

For detailed changes, see CHANGELOG

@better-auth/electron
❗ Breaking Changes
  • Enforced S256 PKCE in the Electron sign-in flow and hardened custom-scheme origin checks (#9645)

    Migration: Upgrade the @better-auth/electron client and server together and add your app's scheme to trustedOrigins. The code_challenge_method parameter and disableOriginOverride option are removed, and host-bearing custom-scheme entries now match that host exactly.

  • Rewrote the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults (#9069)

    Migration: Replace signIn.oauth2({ providerId }) with signIn.social({ provider }), oauth2.link() with linkSocial(), and drop genericOAuthClient(). Callbacks move to /api/auth/callback/:id, pkce now defaults to true, and issuer and requireIssuerValidation are removed in favor of OIDC discovery.

Bug Fixes
  • Fixed client plugin composition so One Tap, Electron, and Expo type-check with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/expo
❗ Breaking Changes
  • Switched Expo secure storage to async access so apps no longer crash when the iOS Keychain is unavailable (#10438)

    Migration: getCookie() now returns a promise, and custom storage implementations must provide both synchronous and asynchronous SecureStore methods. storageAdapter.setItem() stays synchronous, so use setItemAsync() when the write must be awaited.

  • Rewrote the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults (#9069)

    Migration: Replace signIn.oauth2({ providerId }) with signIn.social({ provider }), oauth2.link() with linkSocial(), and drop genericOAuthClient(). Callbacks move to /api/auth/callback/:id, pkce now defaults to true, and issuer and requireIssuerValidation are removed in favor of OIDC discovery.

Bug Fixes
  • Fixed client plugin composition so One Tap, Electron, and Expo type-check with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/stripe
❗ Breaking Changes
  • Made the event parameter of onSubscriptionCancel required (#9531)

    Migration: Declare event as a required parameter in your callback and remove any undefined guards around it.

  • Removed the optional marker from the onSubscriptionCancel event parameter (#9359)

    Migration: event is always supplied, so drop undefined handling from the callback.

For detailed changes, see CHANGELOG

auth
❗ Breaking Changes
  • Accumulated OAuth granted scopes in a new grantedScopes string array (#9825)

    Migration: grantedScopes replaces the comma-joined account.scope string with no read-time fallback, so backfill it from the existing values. The client provider contract is renamed from OAuthProvider to UpstreamProvider.

Features
  • Added a create-admin command for creating an initial admin user (#9547)
  • Added compound table indexes to plugin database schemas (#10402)
Bug Fixes
  • Preserved issuer-scoped account identities by restoring Account.accountId alongside the required issuer (#10668)
  • Exported the generated pgSchema binding so drizzle-kit can emit CREATE SCHEMA for custom PostgreSQL namespaces (#10770)
  • Loaded the auth config through c12 v4 resolveModule, adding support for the export default { auth } shape (#9477)
  • Reverted the granted scopes architecture, restoring the previous account.scope storage (#10123)

For detailed changes, see CHANGELOG

@better-auth/drizzle-adapter
❗ Breaking Changes
  • Moved database joins out of experimental into the stable advanced.database.joins option (#10359)

    Migration: Replace experimental: { joins: true } with advanced: { database: { joins: true } }, and regenerate the schema (npx auth@latest generate) so it includes the required relations.

Features
  • Added compound table indexes to plugin database schemas (#10402)
  • Added a relations-v2 entry point for projects using Drizzle Relations v2 (#9489)
  • Added a schemaName option that generates the Drizzle schema inside a pgSchema namespace (#7169)
Bug Fixes
  • Exported the generated pgSchema binding so drizzle-kit can emit CREATE SCHEMA for custom PostgreSQL namespaces (#10770)

For detailed changes, see CHANGELOG

@better-auth/cimd
❗ Breaking Changes
  • Aligned OAuth client registration and metadata with MCP authorization 2026-07-28 (#10577)

    Migration: Add applicationType and nullable clientDiscoveryId columns, deduplicate existing (clientId, resourceId) links before the new compound unique index, then drop the legacy type and public columns. Client ID Metadata Documents preserve an omitted application_type as null and cannot assign clientCredentialsScopes. Compose mcp() with cimd() for metadata-document clients.

Features
  • Added the @better-auth/cimd Client ID Metadata Document plugin (#9159)
Bug Fixes
  • Client ID Metadata Documents now follow shared-cache freshness rules and fail closed when freshness is ambiguous. The plugin prefers s-maxage over max-age and Expires, honors s-maxage=0, revalidates conditionally with ETag or Last-Modified, and treats invalid or duplicate freshness directives as immediately stale. Concurrent refreshes converge on one client-resource link instead of failing on its unique constraint.

For detailed changes, see CHANGELOG

@better-auth/api-key
❗ Breaking Changes
  • Hardened atomic state transitions so concurrent requests cannot race past single-use or rate-limit guards (#10000)

    Migration: Custom adapters must implement native consumeOne and incrementOne, secondary storage needs atomic consume and increment operations, and custom rate-limit storage makes one consume decision per request. The read-then-delete and read-then-update fallbacks are removed.

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Bug Fixes
  • Fixed SQLite dialect bundles on Kysely 0.29 by mirroring the migration-table constants locally (#10377)
  • Raw database instances (better-sqlite3, node:sqlite, bun:sqlite, mysql2, pg) passed directly as database now get native adapter transactions automatically, matching the behavior of the explicit { db } and { dialect } config shapes. This unblocks plugins that require native transactions (such as @better-auth/scim) when the database is provided in the quickstart database: new Database(...) shape.

For detailed changes, see CHANGELOG

@better-auth/i18n
Features
  • Added built-in translations for 22 languages (#9157)

For detailed changes, see CHANGELOG

@better-auth/mongo-adapter
Features
  • Added compound table indexes to plugin database schemas (#10402)

For detailed changes, see CHANGELOG

@better-auth/passkey
Features
  • Added an optional createSession setting that signs the user in on successful passkey registration (#9873)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@adrianmxb, @brentmitchell25, @bytaesu, @dvanmali, @eluce2, @GautamBytes, @gustavovalverde, @ItalyPaleAle, @jonathansamines, @KingIronMan2011, @momomuchu, @OscarCornish, @pi0, @ping-maxwell, @ruban-s, @sovetski, @yordis

Full changelog: v1.6.30...v1.7.0

View originalPermalink
How v1.7.0 went

v1.6.30

Fixed 3
  • Fixed concurrent cold-start requests from intermittently losing authentication or transaction context due to an async storage initialization race
  • Fixed automatic organization assignment via email domain to require both a verified provider domain and a verified stored user email, preventing social sign-in from joining an organization whose SSO provider merely claims that domain
  • Fixed domain verification to snapshot the provider's domains at request start, returning 409 with SSO_PROVIDER_CHANGED if the provider changes during DNS resolution so callers can reload and retry

From Better Auth

better-auth
Bug Fixes
  • Fixed concurrent cold-start requests from intermittently losing authentication or transaction context due to an async storage initialization race (#10833)

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Fixed automatic organization assignment via email domain to require both a verified provider domain and a verified stored user email, preventing social sign-in from joining an organization whose SSO provider merely claims that domain.
  • Fixed domain verification to snapshot the provider's domains at request start, returning 409 with SSO_PROVIDER_CHANGED if the provider changes during DNS resolution so callers can reload and retry.

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu

Full changelog: v1.6.29...v1.6.30

View originalPermalink
How v1.6.30 went

v1.7.0-rc.6

Pre-release
Fixed 8
  • Restored client plugin declaration compatibility for downstream TypeScript consumers
  • Fixed duplicate session requests during transient Suspense remounts while ensuring incomplete refreshes are revalidated
  • Fixed private_key_jwt client assertions to accept the issuer URL as a valid aud claim (string or array) on token, introspection, and revocation requests
  • Completed the RP-Initiated Logout flow with form-encoded POST support, explicit confirmation pages, and strict post_logout_redirect_uri validation
  • Fixed handling of voluntary and essential ACR requests in authorization flows
  • Fixed claims requests to require the openid scope
  • Fixed missing pgSchema export so drizzle-kit can emit CREATE SCHEMA for custom PostgreSQL namespaces
  • Restored declaration compatibility for downstream TypeScript consumers in @better-auth/electron

From Better Auth

better-auth
Bug Fixes
  • Restored client plugin declaration compatibility for downstream TypeScript consumers. (#10794)
  • Fixed duplicate session requests during transient Suspense remounts while ensuring incomplete refreshes are revalidated. (#10769)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
Bug Fixes
  • Fixed private_key_jwt client assertions to accept the issuer URL as a valid aud claim (string or array) on token, introspection, and revocation requests. (#10811)
  • Completed the RP-Initiated Logout flow with form-encoded POST support, explicit confirmation pages, and strict post_logout_redirect_uri validation. (#10812)
  • Fixed handling of voluntary and essential ACR requests in authorization flows. (#10790)
  • Fixed claims requests to require the openid scope. (#10791)

For detailed changes, see CHANGELOG

@better-auth/drizzle-adapter
Bug Fixes
  • Fixed missing pgSchema export so drizzle-kit can emit CREATE SCHEMA for custom PostgreSQL namespaces. (#10770)

For detailed changes, see CHANGELOG

@better-auth/electron
Bug Fixes
  • Restored declaration compatibility for downstream TypeScript consumers. (#10794)

For detailed changes, see CHANGELOG

@better-auth/expo
Bug Fixes
  • Restored declaration compatibility for downstream TypeScript consumers. (#10794)

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed missing pgSchema export so drizzle-kit can emit CREATE SCHEMA for custom PostgreSQL namespaces. (#10770)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu, @gustavovalverde, @ping-maxwell

Full changelog: v1.7.0-rc.5...v1.7.0-rc.6

View originalPermalink
How v1.7.0-rc.6 went

v1.6.29

Fixed 3
  • Improved deleteSessions performance by running deletes in parallel instead of sequentially
  • Fixed automatic email-domain organization assignment to require both a verified provider domain and a verified user email, preventing social sign-in from granting access when an SSO provider merely claims a domain
  • Fixed domain verification to snapshot the provider's domains at request start, returning 409 with SSO_PROVIDER_CHANGED if the provider changes during DNS verification so callers can reload and retry

From Better Auth

better-auth
Bug Fixes
  • Improved deleteSessions performance by running deletes in parallel instead of sequentially (#10805)

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Fixed automatic email-domain organization assignment to require both a verified provider domain and a verified user email, preventing social sign-in from granting access when an SSO provider merely claims a domain.
  • Fixed domain verification to snapshot the provider's domains at request start, returning 409 with SSO_PROVIDER_CHANGED if the provider changes during DNS verification so callers can reload and retry.

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@Emmaccen

Full changelog: v1.6.28...v1.6.29

View originalPermalink
How v1.6.29 went

v1.6.28

Fixed 2
  • Prevented duplicate session requests during React Suspense retries while preserving revalidation for interrupted refreshes
  • Restored client plugin declaration compatibility for downstream TypeScript consumers

From Better Auth

better-auth
Bug Fixes
  • Prevented duplicate session requests during React Suspense retries while preserving revalidation for interrupted refreshes (#10769)
  • Restored client plugin declaration compatibility for downstream TypeScript consumers (#10794)

For detailed changes, see the CHANGELOG.

@better-auth/electron
Bug Fixes
  • Restored client plugin declaration compatibility for downstream TypeScript consumers (#10794)

For detailed changes, see the CHANGELOG.

@better-auth/expo
Bug Fixes
  • Restored client plugin declaration compatibility for downstream TypeScript consumers (#10794)

For detailed changes, see the CHANGELOG.

Contributors

Thanks to everyone who contributed to this release:

@bytaesu

Full changelog: v1.6.27...v1.6.28

View originalPermalink
How v1.6.28 went

v1.7.0-rc.5

Pre-release
Added 1
  • Added option to disable displayName in the username plugin
Fixed 3
  • Fixed device authorization flow to enforce RFC requirements
  • Fixed type alignment between auth endpoints and better-call
  • Fixed CLI to align installed packages with the running CLI version
Removed 2
  • Replaced standalone deviceCodeGrant() plugin with oauthDeviceAuthorization() used alongside oauthProvider() or mcp()
  • Removed the silenceWarnings config option and startup warnings for well-known metadata endpoints

From Better Auth

better-auth
❗ Breaking Changes
  • Refactored OAuth device grant ownership to use oauthDeviceAuthorization() alongside oauthProvider() or mcp() (#10746)

Migration: Replace the standalone deviceCodeGrant() plugin with oauthDeviceAuthorization() used alongside oauthProvider() or mcp(). Regenerate and apply the schema (resource column is replaced by oauthClientId and resources). Let any pending device codes expire or delete them before upgrading, as they cannot be exchanged through the new integration.

Features
  • Added option to disable displayName in the username plugin (#10330)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
❗ Breaking Changes
  • Refactored OAuth device grant ownership to use oauthDeviceAuthorization() alongside oauthProvider() or mcp() (#10746)

Migration: Replace the standalone deviceCodeGrant() plugin with oauthDeviceAuthorization() used alongside oauthProvider() or mcp(). Regenerate and apply the schema (resource column is replaced by oauthClientId and resources). Let any pending device codes expire or delete them before upgrading, as they cannot be exchanged through the new integration.

Features
  • Removed the silenceWarnings config option and startup warnings for well-known metadata endpoints (#10703)
Bug Fixes
  • Fixed device authorization flow to enforce RFC requirements (#10752)

For detailed changes, see CHANGELOG

@better-auth/scim
Bug Fixes
  • Fixed type alignment between auth endpoints and better-call (#10657)

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed CLI to align installed packages with the running CLI version (#10743)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu, @gustavovalverde, @ping-maxwell

Full changelog: v1.7.0-rc.4...v1.7.0-rc.5

View originalPermalink
How v1.7.0-rc.5 went

v1.6.27

Fixed 3
  • Fixed duplicate session requests being made across Suspense retries
  • Fixed auth endpoint types to align with better-call
  • Fixed the CLI to align installed packages with the running CLI version

From Better Auth

better-auth
Bug Fixes
  • Fixed duplicate session requests being made across Suspense retries (#10676)

For detailed changes, see CHANGELOG

@better-auth/scim
Bug Fixes
  • Fixed auth endpoint types to align with better-call (#10657)

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed the CLI to align installed packages with the running CLI version (#10743)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu

Full changelog: v1.6.26...v1.6.27

View originalPermalink
How v1.6.27 went

v1.7.0-rc.4

Pre-release
Added 1
  • Add a placeholder email utility for generating temporary email addresses
Changed 2
  • Improve Next.js performance by reusing the next/headers import promise in production
  • Switch to async secure storage access to prevent crashes when iOS Keychain is unavailable
Fixed 18
  • Fix sessions not being cleaned up when a user is deleted
  • Fix missing PKCE challenge in Apple OAuth flows
  • Fix duplicate in-flight session requests when React retries a suspended component
  • Fix cookie cache to work independently of the JWT plugin internals
  • Fix findSessions to skip null-parsed session tokens instead of returning early
  • Fix missing verification type when sending email OTP during sign-up

From Better Auth

better-auth
Features
  • Added a placeholder email utility for generating temporary email addresses (#10576)
Bug Fixes
  • Fixed sessions not being cleaned up when a user is deleted (#10520)
  • Fixed missing PKCE challenge in Apple OAuth flows (#10294)
  • Fixed duplicate in-flight session requests when React retries a suspended component (#10676)
  • Fixed cookie cache to work independently of the JWT plugin internals (#10666)
  • Fixed findSessions to skip null-parsed session tokens instead of returning early (#10580)
  • Fixed missing verification type when sending email OTP during sign-up (#10608)
  • Fixed OTP being cleared after password validation errors in email OTP flows (#10552)
  • Fixed email OTP to verify the code before revealing whether the email exists (#10605)
  • Fixed client plugin type inference when using jwtClient (#10513)
  • Fixed JWT signing to use the transaction-scoped adapter (#10623)
  • Fixed Apple user data being lost when using the OAuth proxy (#10599)
  • Fixed Google One Tap to enforce provider signup restrictions (#10479)
  • Fixed client plugin type inference when using oneTapClient (#10635)
  • Fixed rate limit database cleanup to be awaited by default (#10619)
  • Fixed $fetch and $store not being exposed on the Solid client (#10444)
  • Improved Next.js performance by reusing the next/headers import promise in production (#10467)

For detailed changes, see CHANGELOG

@better-auth/expo
❗ Breaking Changes
  • Switched to async secure storage access to prevent crashes when iOS Keychain is unavailable (#10438)

Migration: getCookie() now returns a Promise. Custom storage implementations must provide both sync and async SecureStore methods, and should use setItemAsync() when the write must be awaited.

For detailed changes, see CHANGELOG

@better-auth/redis-storage
Bug Fixes
  • Fixed Redis storage to use SCAN instead of KEYS to avoid blocking the server (#10507)

For detailed changes, see CHANGELOG

@better-auth/scim
Bug Fixes
  • Fixed SCIM PATCH to create filtered attribute values when no target matches, instead of rejecting with a noTarget error (#10682)

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed issuer-scoped account identities being overwritten during OAuth flows (#10668)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@birkskyum, @bytaesu, @Emmaccen, @gustavovalverde, @jashkarangiya, @jeroenvandermerwe, @jlucaso1, @jsj, @krish-vachhani, @mrosberghaus, @XXMOHAMED012

Full changelog: v1.7.0-rc.3...v1.7.0-rc.4

View originalPermalink
How v1.7.0-rc.4 went

v1.6.26

Added 1
  • Added a utility for creating stable, namespaced placeholder emails on the reserved placeholder.invalid domain
Changed 1
  • nextCookies performance improved in instrumented Next.js applications by reusing the next/headers import promise
Fixed 11
  • Session cleanup on user deletion now also removes sessions from secondary storage
  • findSessions skips invalid secondary-storage session entries without discarding other valid sessions
  • Email OTP sign-up passes the verification type to custom OTP generators
  • Email OTP password reset allows retrying after entering an invalid password
  • Email OTP verification no longer reveals whether an email is registered before the OTP is verified
  • jwtClient() no longer collapses createAuthClient type inference when combined with other client plugins

From Better Auth

better-auth
Bug Fixes
  • Fixed session cleanup on user deletion to also remove sessions from secondary storage (#10520)
  • Fixed findSessions to skip invalid secondary-storage session entries without discarding other valid sessions (#10580)
  • Fixed email OTP sign-up to pass the verification type to custom OTP generators (#10608)
  • Fixed email OTP password reset to allow retrying after entering an invalid password (#10552)
  • Fixed email OTP verification to no longer reveal whether an email is registered before the OTP is verified (#10605)
  • Fixed jwtClient() collapsing createAuthClient type inference when combined with other client plugins (#10513)
  • Fixed JWT key minting inside database transactions to use the transaction-scoped adapter, preventing deadlocks on SQLite and ensuring keys commit with their surrounding transaction on Postgres and MySQL (#10623)
  • Fixed oAuthProxy to preserve Apple user data from form_post callbacks (#10599)
  • Fixed oneTapClient() collapsing createAuthClient type inference when combined with other client plugins (#10635)
  • Fixed database rate-limit cleanup to complete when no background task handler is configured (#10619)
  • Improved nextCookies performance in instrumented Next.js applications by reusing the next/headers import promise (#10467)

For detailed changes, see CHANGELOG

@better-auth/core
Features
  • Added a utility for creating stable, namespaced placeholder emails on the reserved placeholder.invalid domain (#10576)

For detailed changes, see CHANGELOG

@better-auth/redis-storage
Bug Fixes
  • Fixed listKeys() and clear() to use SCAN instead of KEYS so large keyspaces no longer block the Redis server (#10507)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@bytaesu, @Emmaccen, @gustavovalverde, @jashkarangiya, @jeroenvandermerwe, @jlucaso1, @krish-vachhani, @mrosberghaus, @XXMOHAMED012

Full changelog: v1.6.25...v1.6.26

View originalPermalink
How v1.6.26 went

v1.7.0-rc.3

Pre-release
Added 3
  • Added database indexes for device and user code lookups in the device authorization flow
  • Added RP-initiated logout support for Generic OAuth providers, redirecting users to their OpenID provider's logout endpoint on sign-out
  • Added RFC 8628 device authorization grant support to the OAuth provider, enabling device-flow token exchanges for registered OAuth clients
Changed 2
  • Changed Microsoft and microsoftEntraId accounts to use the stable oid claim as the account identifier instead of sub
  • Aligned MCP authorization with the 2026-07-28 specification, replacing legacy type/public client fields with applicationType and enforcing stricter redirect validation and scope controls
Fixed 5
  • Enabled native database transactions in test instances for Postgres and MySQL databases
  • Fixed a deadlock during sign-up when JWT session caching and native transactions are both enabled on single-connection databases
  • Fixed TypeScript errors when composing One Tap, Electron, and Expo plugins with createAuthClient
  • Fixed MCP scope enforcement to return a 403 with an RFC 6750 insufficient_scope WWW-Authenticate challenge listing all missing scopes, allowing clients to request them in a single authorization request
  • Fixed Client ID Metadata Document caching to follow shared-cache freshness rules, preferring s-maxage, honoring conditional revalidation, and treating ambiguous or duplicate freshness directives as immediately stale to prevent unique-constraint failures on concurrent refreshes

From Better Auth

better-auth
❗ Breaking Changes
  • Added database indexes for device and user code lookups in the device authorization flow (#10059)

Migration: MySQL and SQL Server users must convert device code and user code columns to bounded strings (max 191 characters) and resolve any oversized values before applying the migration.

  • Changed Microsoft and microsoftEntraId accounts to use the stable oid claim as the account identifier instead of sub (#10204)

Migration: Migrate existing Microsoft account rows keyed by sub to oid before upgrading; tokens without a valid oid claim are rejected after the update.

Features
  • Added RP-initiated logout support for Generic OAuth providers, redirecting users to their OpenID provider's logout endpoint on sign-out (#9368)
  • Added RFC 8628 device authorization grant support to the OAuth provider, enabling device-flow token exchanges for registered OAuth clients (#10135)
Bug Fixes
  • Enabled native database transactions in test instances for Postgres and MySQL databases.
  • Fixed a deadlock during sign-up when JWT session caching and native transactions are both enabled on single-connection databases (#10622)
  • Fixed TypeScript errors when composing One Tap, Electron, and Expo plugins with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/core
❗ Breaking Changes
  • Changed Microsoft and microsoftEntraId accounts to use the stable oid claim as the account identifier instead of sub (#10204)

Migration: Migrate existing Microsoft account rows keyed by sub to oid before upgrading; tokens without a valid oid claim are rejected after the update.

Features
  • Added RP-initiated logout support for Generic OAuth providers, redirecting users to their OpenID provider's logout endpoint on sign-out (#9368)
Bug Fixes
  • Fixed TypeScript errors when composing One Tap, Electron, and Expo plugins with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
❗ Breaking Changes
  • Aligned MCP authorization with the 2026-07-28 specification, replacing legacy type/public client fields with applicationType and enforcing stricter redirect validation and scope controls (#10577)

Migration: Apply a database migration to add applicationType, nullable clientDiscoveryId, and clientCredentialsScopes columns; backfill applicationType from existing values and clientCredentialsScopes to [] for all clients; deduplicate (clientId, resourceId) pairs before applying the new unique index. Replace OAuthClient string-index accesses with OAuthClient & YourExtensionMetadata, and compose mcp() with cimd() explicitly if using Client ID Metadata Documents.

Features
  • Added RFC 8628 device authorization grant support to the OAuth provider, enabling device-flow token exchanges for registered OAuth clients (#10135)
Bug Fixes
  • Fixed MCP scope enforcement to return a 403 with an RFC 6750 insufficient_scope WWW-Authenticate challenge listing all missing scopes, allowing clients to request them in a single authorization request.

For detailed changes, see CHANGELOG

@better-auth/cimd
❗ Breaking Changes
  • Aligned MCP authorization with the 2026-07-28 specification, replacing legacy type/public client fields with applicationType and enforcing stricter redirect validation and scope controls (#10577)

Migration: Apply a database migration to add applicationType, nullable clientDiscoveryId, and clientCredentialsScopes columns; backfill applicationType from existing values and clientCredentialsScopes to [] for all clients; deduplicate (clientId, resourceId) pairs before applying the new unique index. Replace OAuthClient string-index accesses with OAuthClient & YourExtensionMetadata, and compose mcp() with cimd() explicitly if using Client ID Metadata Documents.

Bug Fixes
  • Fixed Client ID Metadata Document caching to follow shared-cache freshness rules, preferring s-maxage, honoring conditional revalidation, and treating ambiguous or duplicate freshness directives as immediately stale to prevent unique-constraint failures on concurrent refreshes.

For detailed changes, see CHANGELOG

@better-auth/mcp
❗ Breaking Changes
  • Aligned MCP authorization with the 2026-07-28 specification, replacing legacy type/public client fields with applicationType and enforcing stricter redirect validation and scope controls (#10577)

Migration: Apply a database migration to add applicationType, nullable clientDiscoveryId, and clientCredentialsScopes columns; backfill applicationType from existing values and clientCredentialsScopes to [] for all clients; deduplicate (clientId, resourceId) pairs before applying the new unique index. Replace OAuthClient string-index accesses with OAuthClient & YourExtensionMetadata, and compose mcp() with cimd() explicitly if using Client ID Metadata Documents.

For detailed changes, see CHANGELOG

@better-auth/scim
Features
  • Added SCIM Enterprise User extension attributes and classic user attributes with improved Microsoft Entra interoperability (#10620)
  • Added a managed connection catalog for runtime SCIM tenant connection and credential management through server-only auth.api methods (#10592)
Bug Fixes
  • Fixed Microsoft Entra interoperability by accepting case-insensitive string boolean values for the SCIM active field and primary sub-attributes of emails, phoneNumbers, addresses, roles, and entitlements at the HTTP ingress.
  • Added managedConnections configuration to allow trusted server code to create, issue, rotate, and revoke SCIM tenant connections and bearer credentials through server-only auth.api methods without a code-defined connection.
  • Fixed dynamic SCIM connection decommissioning to allow retaining a terminal connection binding by supplying the provisioning domain before the first authenticated request.

For detailed changes, see CHANGELOG

@better-auth/sso
Features
  • Extended resolveUser to SAML sign-ins with a discriminated protocol field, and added guardProviderMutation to authorize SSO provider updates and deletions (#10621)
Bug Fixes
  • Fixed SSO provider registration to allow reusing a SCIM connection ID, since SCIM connections no longer participate in the authentication provider namespace.
  • Improved SAML security by verifying assertion signatures on the raw XML rather than a pre-parsed response, enforcing signing policy and size limits on SP metadata, and correcting wantAssertionsSigned to control assertion signing instead of response signing.

For detailed changes, see CHANGELOG

@better-auth/electron
Bug Fixes
  • Fixed TypeScript errors when composing One Tap, Electron, and Expo plugins with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/expo
Bug Fixes
  • Fixed TypeScript errors when composing One Tap, Electron, and Expo plugins with createAuthClient (#10505)

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Bug Fixes
  • Fixed raw database instances (better-sqlite3, node:sqlite, bun:sqlite, mysql2, pg) passed directly as database to automatically receive native adapter transactions, matching the explicit { db }/{ dialect } config shapes and unblocking plugins like @better-auth/scim when using the quickstart configuration.

For detailed changes, see CHANGELOG

@better-auth/passkey
Features
  • Added an optional createSession setting to passkey registration that signs the user in automatically on successful registration (#9873)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@brentmitchell25, @GautamBytes, @gustavovalverde

Full changelog: v1.7.0-rc.2...v1.7.0-rc.3

View originalPermalink
How v1.7.0-rc.3 went

v1.6.25

Fixed 4
  • Fixed Apple OAuth not sending the PKCE code challenge during authorization, causing token exchange failures
  • Fixed Google One Tap creating new users when sign-up was disabled on the Google provider
  • Fixed $fetch and $store not being exposed on the Solid client
  • Fixed internal adapter queries being routed to the wrong table when a built-in table's modelName was set to another table's schema key

From Better Auth

better-auth
Bug Fixes
  • Fixed Apple OAuth not sending the PKCE code challenge during authorization, causing token exchange failures (#10294)
  • Fixed Google One Tap creating new users when sign-up was disabled on the Google provider (#10479)
  • Fixed $fetch and $store not being exposed on the Solid client (#10444)
  • Fixed internal adapter queries being routed to the wrong table when a built-in table's modelName was set to another table's schema key (e.g. user.modelName = "account").

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@birkskyum, @jsj, @krish-vachhani

Full changelog: v1.6.24...v1.6.25

View originalPermalink
How v1.6.25 went

v1.7.0-rc.2

Pre-release
Added 5
  • Add ctx parameter to verifyIdToken
  • Add compound table indexes to database
  • Add beforeStoreCookie option to last-login-method plugin for GDPR compliance
  • Add getOrganization method for metadata-only fetches in organization plugin
  • Add transactional OIDC user resolution to SSO
Changed 12
  • Move joins configuration from experimental.joins to advanced.database.joins
  • Rename Account.accountId to Account.providerAccountId and make Account.issuer required
  • Change account-specific APIs to select Account.id through accountId parameter
  • Use token and provider-profile APIs with useAccountCookie option for signed account cookie selection
  • Use local:credential as credential accounts provider identity
  • OAuth provider identity now comes from raw verified profiles with OIDC using sub and plain OAuth using id
Fixed 3
  • Add no-cache cache control headers to get-session endpoint
  • Recognize BIGINT as valid number type for SQLite in migrations
  • Handle request clone failures in callbacks
Removed 2
  • Remove mapping.id from OIDC and SAML configurations
  • Remove SCIM configuration, client APIs, database schema, and organization-backed Group model

From Better Auth

better-auth
❗ Breaking Changes
  • chore!: move joins to advanced.database.joins (#10359)

    If you previously set experimental: { joins: true }, update your config to:

    advanced: {
      database: {
        joins: true,
      },
    }
    

    Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (npx auth@latest generate).

  • feat(auth)!: scope accounts by issuer (#10403)

    This release is breaking. Account.accountId is renamed to Account.providerAccountId, and Account.issuer is required. Account-specific APIs select the local Account.id through accountId; token and provider-profile APIs can instead select the signed account cookie with useAccountCookie: true. Credential accounts use local:credential and the linked user's stable id as their provider identity.

    OAuth provider identity now comes from raw verified profiles. OpenID Connect discovery uses sub, plain OAuth uses id, and providers can declare accountSubject for another immutable field; Better Auth no longer switches between sub and id at runtime. getUserInfo().user no longer carries provider identity, and mapProfileToUser cannot return id. Read the selected identity from accountInfo.account.providerAccountId instead of accountInfo.user.id. The generic microsoftEntraId helper now requires a concrete tenant GUID; use the built-in Microsoft provider for multi-tenant authorities.

    SSO account subjects are now protocol-defined. OIDC uses the verified sub claim, and SAML uses the signed NameID; mapping.id is removed from both configurations. A manual SAML configuration without metadata XML must set idpMetadata.entityID, because samlConfig.issuer identifies the service provider and no longer acts as the IdP identity.

    Apply the reviewed account-identity backfill in the Better Auth 1.7 upgrade guide before deploying. The generated schema migration cannot assign trusted issuers or resolve existing identity collisions automatically.

  • feat(scim)!: decouple provisioning from the organization plugin (#10390)

    This replaces the previous SCIM configuration, client APIs, database schema, and organization-backed Group model. Existing SCIM installations cannot migrate provisioning state in place. Follow the SCIM cutover in the 1.7 upgrade guide, including full directory reprovisioning, before resuming traffic.

    Deferred database side effects now run only after a successful transaction. A rolled-back User update no longer refreshes its cached profile, and a rolled-back bulk session revocation no longer invalidates sessions.

Features
  • feat: add ctx to verifyIdToken (#10376)
  • feat(db): add compound table indexes (#10402)
  • feat(last-login-method): beforeStoreCookie option for GDPR compliance (#5753)
  • feat(organization): add getOrganization for metadata-only fetches (#10397)
  • feat(sso): add transactional OIDC user resolution (#10473)
Bug Fixes
  • chore: widen drizzle-kit peer dependency range (#10299)
  • fix: get-session should have no-cache cache control headers (#10222)
  • fix: recognize BIGINT as valid number type for SQLite in migrations. (#10316)
  • fix(auth): handle request clone failures in callbacks (#10336)
  • fix(client): preserve null in useSession().data type with throw:true (#9787)
  • fix(client): restore auth query lifecycle after remount (#10379)
  • fix(cookies): tighten CookieAttributes index signature type (#10441) (#10442)
  • fix(core): dedup request-state AsyncLocalStorage init to fix intermittent "No request state found" (#9862)
  • fix(core): resolve user.modelName collisions in references and adapter (#10235)
  • fix(db): avoid duplicate unique indexes in kysely migrations (#10357)
  • fix(magic-link, email-otp): force-validate Origin on cookieless send endpoints (#10368)
  • fix(mcp): expose remote auth challenge headers (#10290)
  • fix(open-api): include plugin user fields on sign-up/update bodies (#10453)
  • fix(organization): apply membershipLimit to listMembers user fetch (#10342)
  • fix(organization): let the database generate invitation ids (#10040)
  • fix(siwe): issue addressless nonces (#10234)

For detailed changes, see CHANGELOG

@better-auth/core
❗ Breaking Changes
  • chore!: move joins to advanced.database.joins (#10359)

    If you previously set experimental: { joins: true }, update your config to:

    advanced: {
      database: {
        joins: true,
      },
    }
    

    Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (npx auth@latest generate).

  • feat(auth)!: scope accounts by issuer (#10403)

    This release is breaking. Account.accountId is renamed to Account.providerAccountId, and Account.issuer is required. Account-specific APIs select the local Account.id through accountId; token and provider-profile APIs can instead select the signed account cookie with useAccountCookie: true. Credential accounts use local:credential and the linked user's stable id as their provider identity.

    OAuth provider identity now comes from raw verified profiles. OpenID Connect discovery uses sub, plain OAuth uses id, and providers can declare accountSubject for another immutable field; Better Auth no longer switches between sub and id at runtime. getUserInfo().user no longer carries provider identity, and mapProfileToUser cannot return id. Read the selected identity from accountInfo.account.providerAccountId instead of accountInfo.user.id. The generic microsoftEntraId helper now requires a concrete tenant GUID; use the built-in Microsoft provider for multi-tenant authorities.

    SSO account subjects are now protocol-defined. OIDC uses the verified sub claim, and SAML uses the signed NameID; mapping.id is removed from both configurations. A manual SAML configuration without metadata XML must set idpMetadata.entityID, because samlConfig.issuer identifies the service provider and no longer acts as the IdP identity.

    Apply the reviewed account-identity backfill in the Better Auth 1.7 upgrade guide before deploying. The generated schema migration cannot assign trusted issuers or resolve existing identity collisions automatically.

  • feat(scim)!: decouple provisioning from the organization plugin (#10390)

    This replaces the previous SCIM configuration, client APIs, database schema, and organization-backed Group model. Existing SCIM installations cannot migrate provisioning state in place. Follow the SCIM cutover in the 1.7 upgrade guide, including full directory reprovisioning, before resuming traffic.

    Deferred database side effects now run only after a successful transaction. A rolled-back User update no longer refreshes its cached profile, and a rolled-back bulk session revocation no longer invalidates sessions.

Features
  • feat(db): add compound table indexes (#10402)
  • feat(sso): add transactional OIDC user resolution (#10473)

For detailed changes, see CHANGELOG

@better-auth/drizzle-adapter
❗ Breaking Changes
  • chore!: move joins to advanced.database.joins (#10359)

    If you previously set experimental: { joins: true }, update your config to:

    advanced: {
      database: {
        joins: true,
      },
    }
    

    Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (npx auth@latest generate).

Features
  • feat(db): add compound table indexes (#10402)
  • feat(drizzle): generate drizzle schema with schema namespace (#7169)

For detailed changes, see CHANGELOG

@better-auth/sso
❗ Breaking Changes
  • feat(auth)!: scope accounts by issuer (#10403)

    This release is breaking. Account.accountId is renamed to Account.providerAccountId, and Account.issuer is required. Account-specific APIs select the local Account.id through accountId; token and provider-profile APIs can instead select the signed account cookie with useAccountCookie: true. Credential accounts use local:credential and the linked user's stable id as their provider identity.

    OAuth provider identity now comes from raw verified profiles. OpenID Connect discovery uses sub, plain OAuth uses id, and providers can declare accountSubject for another immutable field; Better Auth no longer switches between sub and id at runtime. getUserInfo().user no longer carries provider identity, and mapProfileToUser cannot return id. Read the selected identity from accountInfo.account.providerAccountId instead of accountInfo.user.id. The generic microsoftEntraId helper now requires a concrete tenant GUID; use the built-in Microsoft provider for multi-tenant authorities.

    SSO account subjects are now protocol-defined. OIDC uses the verified sub claim, and SAML uses the signed NameID; mapping.id is removed from both configurations. A manual SAML configuration without metadata XML must set idpMetadata.entityID, because samlConfig.issuer identifies the service provider and no longer acts as the IdP identity.

    Apply the reviewed account-identity backfill in the Better Auth 1.7 upgrade guide before deploying. The generated schema migration cannot assign trusted issuers or resolve existing identity collisions automatically.

Features
  • feat(sso): add transactional OIDC user resolution (#10473)
Bug Fixes
  • fix(sso): redirect idp initiated saml flows in split origin deployments (#10388)

For detailed changes, see CHANGELOG

@better-auth/scim
❗ Breaking Changes
  • feat(scim)!: decouple provisioning from the organization plugin (#10390)

    This replaces the previous SCIM configuration, client APIs, database schema, and organization-backed Group model. Existing SCIM installations cannot migrate provisioning state in place. Follow the SCIM cutover in the 1.7 upgrade guide, including full directory reprovisioning, before resuming traffic.

    Deferred database side effects now run only after a successful transaction. A rolled-back User update no longer refreshes its cached profile, and a rolled-back bulk session revocation no longer invalidates sessions.

Features
  • feat(scim): expose active provisioned user links (#10474)

For detailed changes, see CHANGELOG

auth
Features
  • feat(db): add compound table indexes (#10402)
Bug Fixes
  • fix: stub SvelteKit's explicit-environment-variables modules (#10221)
  • fix(cli): avoid duplicate unique indexes in drizzle schema (#10333)
  • fix(cli): disambiguate Drizzle relations with relationName (#10352)
  • fix(cli): recover when auth generate's config self-imports its own output (#10302)

For detailed changes, see CHANGELOG

@better-auth/electron
Bug Fixes
  • fix(electron): forward each Set-Cookie from /electron/init-oauth-proxy individually (#9672)

For detailed changes, see CHANGELOG

@better-auth/kysely-adapter
Bug Fixes
  • fix(kysely-adapter): restore local migration constants (#10377)

For detailed changes, see CHANGELOG

@better-auth/mongo-adapter
Features
  • feat(db): add compound table indexes (#10402)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider
Bug Fixes
  • fix(oauth-provider): defer logout effects until commit (#10472)

For detailed changes, see CHANGELOG

@better-auth/stripe
Bug Fixes
  • fix(organization): pass endpoint context to organization delete hooks (#10190)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@akshatmalik-bruh, @ayushman46, @c-nicol, @gaurav-init, @gaurav0107, @GautamBytes, @gustavovalverde, @momomuchu, @OrangeManLi, @paoloricciuti, @ping-maxwell, @shiminshen, @swithek, @Tushar-Khandelwal-2004, @vinay-oppuri

Full changelog: v1.7.0-rc.1...v1.7.0-rc.2

View originalPermalink
How v1.7.0-rc.2 went

v1.6.24

Added 2
  • Request context (ctx) as a third argument to verifyIdToken, enabling custom ID token verifiers to read request headers
  • beforeStoreCookie option to the last-login-method plugin for GDPR compliance
Fixed 19
  • get-session endpoint to include no-store cache control headers, preventing stale session data from being served
  • SQLite migration diffs to recognize BIGINT as a valid number type, preventing spurious pending changes on rate limiter columns
  • Auth requests failing when request cloning throws an error inside verification callbacks
  • useSession({ throw: true }) incorrectly excluding null from its data type
  • Auth query revalidation and signal listeners not being restored after a client component remounts
  • CookieAttributes index signature type to be more precise

From Better Auth

better-auth
Features
  • Added request context (ctx) as a third argument to verifyIdToken, enabling custom ID token verifiers to read request headers (#10376)
  • Added beforeStoreCookie option to the last-login-method plugin for GDPR compliance (#5753)
Bug Fixes
  • Replaced flaky MongoDB where-coercion integration test with a direct unit test for more reliable test runs (#10369)
  • Fixed the get-session endpoint to include no-store cache control headers, preventing stale session data from being served (#10222)
  • Fixed SQLite migration diffs to recognize BIGINT as a valid number type, preventing spurious pending changes on rate limiter columns (#10316)
  • Fixed auth requests failing when request cloning throws an error inside verification callbacks (#10336)
  • Fixed useSession({ throw: true }) incorrectly excluding null from its data type (#9787)
  • Fixed auth query revalidation and signal listeners not being restored after a client component remounts (#10379)
  • Fixed the CookieAttributes index signature type to be more precise (#10442)
  • Fixed silent misrouting of adapter queries when user.modelName was set to a value that collides with another schema key (#10235)
  • Fixed Kysely migration generation producing duplicate indexes for fields marked both unique and index (#10357)
  • Fixed magic-link and email-OTP send endpoints to validate the Origin header on cookieless requests, preventing cross-origin abuse (#10368)
  • Fixed remote MCP auth 401 challenge headers being hidden from browser clients due to missing CORS exposure (#10290)
  • Fixed OpenAPI schema to include plugin user fields (such as username and displayUsername) in /sign-up/email and /update-user request bodies (#10453)
  • Fixed organization.listMembers failing with "User not found for member" for organizations with more than ~100 members (#10342)
  • Fixed organization invitations to use database-generated IDs when advanced.database.generateId is configured, matching the behavior of other models (#10040)
  • Fixed getDefaultModelName to prefer exact schema key matches over modelName aliases, preventing adapter queries from being misrouted when a built-in table's name collides with another schema key

For detailed changes, see CHANGELOG

auth
Bug Fixes
  • Fixed SvelteKit builds by stubbing explicit-environment-variables modules (#10221)
  • Fixed Drizzle schema generation producing duplicate indexes for fields marked both unique and index (#10333)
  • Fixed Drizzle schema generation for tables with multiple foreign keys to the same model by adding disambiguating relationName values (#10352)
  • Fixed auth generate failing when the config file imports the not-yet-generated output file (e.g. on a Convex first run) (#10302)

For detailed changes, see CHANGELOG

@better-auth/electron
Bug Fixes
  • Updated compatibility testing to include Electron 43 (peer range unchanged at >=36.0.0) (#10440)
  • Fixed /electron/init-oauth-proxy forwarding multiple Set-Cookie headers as a single comma-joined string, which caused the browser to drop the transfer-token cookie during OAuth handoff (#9672)

For detailed changes, see CHANGELOG

@better-auth/core
Bug Fixes
  • Fixed an intermittent "No request state found" error caused by a race condition in AsyncLocalStorage initialization on serverless cold starts (e.g. Cloudflare Workers) (#9862)

For detailed changes, see CHANGELOG

@better-auth/sso
Bug Fixes
  • Fixed IdP-initiated SAML sign-ins in split-origin deployments to redirect users to the configured application URL instead of the authentication server, using idpInitiatedCallbackUrl (#10388)

For detailed changes, see CHANGELOG

@better-auth/stripe
Bug Fixes
  • Fixed beforeDeleteOrganization and afterDeleteOrganization hooks not receiving the endpoint context as the second argument (#10190)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@akshatmalik-bruh, @ayushman46, @c-nicol, @gaurav-init, @gaurav0107, @GautamBytes, @momomuchu, @OrangeManLi, @paoloricciuti, @ping-maxwell, @shiminshen, @swithek, @Tushar-Khandelwal-2004, @vinay-oppuri

Full changelog: v1.6.23...v1.6.24

View originalPermalink
How v1.6.24 went

v1.7.0-rc.1

Pre-release
Added 1
  • Add Yandex as a supported OAuth social provider
Fixed 3
  • Fix auth migrate to no longer abort when adding required or unique columns to an existing table
  • Fix affected row counting for D1 and postgres-js adapters
  • Fix string default values to be properly escaped in generated Drizzle schema

From Better Auth

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

View originalPermalink
How v1.7.0-rc.1 went

v1.6.23

Added 1
  • Add Yandex as a social OAuth provider
Fixed 3
  • Fix affected row counting for D1 and postgres-js adapters
  • Fix organization subscription actions (cancel, upgrade, restore, and the billing portal) that could act on the wrong organization
  • Fix string default values not being properly escaped in the generated Drizzle schema

From Better Auth

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

View originalPermalink
How v1.6.23 went

v1.7.0-rc.0

Pre-release
Changed 19
  • Support wildcard endpoint matching in captcha
  • Ship MCP as its own package built on the OAuth provider, renaming route helper to requireMcpAuth and remote client to createMcpResourceClient
  • Add OIDC back-channel logout to oauth-provider, notifying Relying Parties when a user's session ends and invalidating access tokens immediately
  • Revoke refresh tokens without offline_access on session end while preserving offline_access refresh tokens for long-lived API access
  • Add backchannel_logout_supported and backchannel_logout_session_supported to OIDC discovery endpoints when JWT plugin is enabled
  • Model OAuth protected resources explicitly by removing validAudiences and moving resource identifiers to resources configuration

From Better Auth

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 | null

    better-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 | null

    better-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, @yordis

Full changelog: v1.6.22...v1.7.0-rc.0

View originalPermalink
How v1.7.0-rc.0 went

v1.7.0-beta.10

Pre-release
Fixed 20
  • Bundled dependencies were refreshed to their latest compatible releases, including jose, nanostores, the noble crypto packages, and SimpleWebAuthn
  • Rate limiting is now applied before plugin request handlers run
  • Unproven credentials are now revoked when signing in via magic link or email OTP
  • Account-linking logs are now routed through the configured logger
  • Admin authorization now uses authoritative session reads
  • TypeScript inference errors from inherited APIError properties have been resolved

From Better Auth

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

View originalPermalink
How v1.7.0-beta.10 went

v1.6.22

Added 1
  • Account-level verification lockout for two-factor authentication
Fixed 4
  • Unproven credentials are now revoked during magic link and email OTP sign-in
  • Server-side OAuth requests now refuse redirect responses instead of following them
  • SCIM write-path operations are now properly scoped and correctly honor the active attribute
  • Organization subscription actions (cancel, upgrade, restore, and the billing portal) no longer act on the wrong organization

From Better Auth

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

View originalPermalink
How v1.6.22 went

v1.6.21

Fixed 19
  • Rate limits are now enforced before plugin request handlers run
  • Admin permission changes and bans now take effect immediately even when session cookie cache is enabled
  • deviceAuthorization() no longer throws a ZodError when called without a schema option under Zod v4
  • Google hosted-domain validation now applies consistently across all sign-in flows including Google One Tap
  • OAuth proxy now rejects profile callbacks that do not match an issued OAuth state, preventing session creation with stale state
  • OAuth sign-up and account linking now ignore provider profile values for fields marked input: false

From Better Auth

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 CHANGELOG

auth
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 CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@Bekacru, @benpsnyder, @bytaesu, @gustavovalverde, @moonevm, @Paola3stefania, @ping-maxwell, @rachit367

Full changelog: v1.6.20...v1.6.21

View originalPermalink
How v1.6.21 went

v1.7.0-beta.9

Pre-release
Added 2
  • Added support for confidential DCR clients to complete authorization-code flows without PKCE when clientRegistrationRequirePKCE: false is set
  • Added support for the claims.userinfo authorization request parameter, allowing clients to request specific standard claims from the UserInfo endpoint
Fixed 9
  • Fixed the UserInfo endpoint to accept bearer tokens in application/x-www-form-urlencoded POST request bodies
  • Fixed confidential clients that opted out of PKCE to successfully request offline_access when the authorization includes both openid scope and a nonce
  • Fixed the OIDC authorization endpoint to accept form-encoded POST requests and return proper errors for unsupported request and request_uri parameters
  • Fixed the UserInfo endpoint to correctly return profile and email scope claims, and added rejection of unsupported acr_values in authorization requests
  • Fixed the token endpoint to only require redirect_uri when the original authorization request included one, and to return invalid_grant on mismatches
  • Fixed Dynamic Client Registration to preserve client key metadata across updates
Removed 1
  • Restricted customIdTokenClaims, extension ID-token claims, and per-issuance idTokenClaims from overriding protected OIDC/JWT protocol claims (iss, sub, aud, exp, nonce, auth_time, acr, amr, azp)

From Better Auth

@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

View originalPermalink
How v1.7.0-beta.9 went
View all

Discussion

If you publish Better Auth, you can claim this product by proving you administer its repository.