v1.7.0
semaphoreui/semaphorev1.7.0Aug 18, 2026by better-release[bot]
AI Summary
A major version update moving database joins to stable, reorganizing the MCP plugin, and rewriting OAuth features with new defaults.
Key Highlights
- Database joins moved from experimental to stable `advanced.database.joins` option.
- MCP plugin moved to its own `@better-auth/mcp` package built on the OAuth provider.
- Rewrite of the generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults.
- Added OIDC back-channel logout support.
Breaking Changes
- Moved database joins from experimental to stable `advanced.database.joins`.
- Scoped account identity by trusted issuer (requires `Account.issuer`).
- MCP plugin moved to `@better-auth/mcp` package.
- SCIM provisioning decoupled from the organization plugin.
- Removed deprecated `oidcProvider` plugin.
- Generic OAuth plugin rewritten as `signIn.social()` and `linkSocial()`.
New Features
- Added `clientAssertion` support to Microsoft Entra ID social provider.
- Added `user.validateUserInfo` gate for rejecting identities.
- Added `hydrateSession` for server-fetched data on first render.
- Added `allowIdpInitiated` support for IdP-initiated flows.
- Added RP-initiated logout support.
Full Release Notes
**Blog post:** [Better Auth 1.7](https://better-auth.com/blog/1-7)
## `better-auth`
### ❗ Breaking Changes
- Moved database joins out of `experimental` into the stable `advanced.database.joins` option ([#10359](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10036))
> **Migration:** Configure `oneTap({ clientId })` or `socialProviders.google.clientId`.
- Removed the deprecated `oidcProvider` plugin ([#10031](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9898))
- Made the `Auth` instance directly fetchable ([#9431](https://github.com/better-auth/better-auth/pull/9431))
- Added per-provider `requireEmailVerification` for social sign-in ([#9929](https://github.com/better-auth/better-auth/pull/9929))
- Added a `user.validateUserInfo` gate for rejecting an identity before a user is created or linked ([#9864](https://github.com/better-auth/better-auth/pull/9864))
- Added `hydrateSession` so `useSession` returns server-fetched data on the first render ([#8733](https://github.com/better-auth/better-auth/pull/8733))
- Added compound table indexes to plugin database schemas ([#10402](https://github.com/better-auth/better-auth/pull/10402))
- Added `allowIdpInitiated` support for IdP-initiated flows through a secure server-side bounce ([#9301](https://github.com/better-auth/better-auth/pull/9301))
- Added RP-initiated logout so `signOut()` can also sign users out of the OpenID provider ([#9368](https://github.com/better-auth/better-auth/pull/9368))
- Added `refreshTokenParams` for forwarding extra parameters on generic OAuth token refresh ([#9948](https://github.com/better-auth/better-auth/pull/9948))
- Verified discovery `id_tokens` against the provider JWKS and enabled `id_token` sign-in for generic OAuth ([#9966](https://github.com/better-auth/better-auth/pull/9966))
- Added the OAuth device authorization grant (RFC 8628) ([#10135](https://github.com/better-auth/better-auth/pull/10135))
- Added DPoP sender-constrained access tokens (RFC 9449) ([#10039](https://github.com/better-auth/better-auth/pull/10039))
- Added the `at_hash` claim to ID tokens issued alongside an access token, per OIDC Core §3.1.3.6 ([#9079](https://github.com/better-auth/better-auth/pull/9079))
- Added `private_key_jwt` client authentication for token endpoint requests (RFC 7523) ([#8836](https://github.com/better-auth/better-auth/pull/8836))
- Sent `Cache-Control: no-store` on every OAuth response that carries credentials ([#10065](https://github.com/better-auth/better-auth/pull/10065))
- Added per-request `additionalParams` and `loginHint` across `signIn.social`, `linkSocial`, and `signIn.sso` ([#9305](https://github.com/better-auth/better-auth/pull/9305))
- Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers ([#9930](https://github.com/better-auth/better-auth/pull/9930))
- Allowed passing `userId` and `organizationId` to the `listUserTeams` API ([#8977](https://github.com/better-auth/better-auth/pull/8977))
- Added `organization.getOrganization()` for metadata-only fetches ([#10397](https://github.com/better-auth/better-auth/pull/10397))
- Added a server-only `consumePhoneNumberOTP` API for custom phone OTP flows ([#9766](https://github.com/better-auth/better-auth/pull/9766))
- Added JWKS-backed asymmetric JWTs for the session cookie cache ([#8931](https://github.com/better-auth/better-auth/pull/8931))
- Added transactional OIDC user resolution for SSO sign-ins ([#10473](https://github.com/better-auth/better-auth/pull/10473))
- Added an immutable username option ([#9240](https://github.com/better-auth/better-auth/pull/9240))
- Allowed omitting the username plugin's separate `displayUsername` field ([#10330](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10299))
- Decoupled the session cookie cache from JWT plugin internals ([#10666](https://github.com/better-auth/better-auth/pull/10666))
- Allowed `auth migrate` to add required columns with static defaults and nullable unique columns to existing tables ([#10293](https://github.com/better-auth/better-auth/pull/10293))
- Bound the ID token `nonce` to the authorization request in the generic OAuth redirect flow ([#10095](https://github.com/better-auth/better-auth/pull/10095))
- Fixed a sign-up deadlock when JWT cookie caching ran on a single-connection SQLite database with native transactions ([#10622](https://github.com/better-auth/better-auth/pull/10622))
- Created new OAuth accounts inside the user creation transaction ([#10125](https://github.com/better-auth/better-auth/pull/10125))
- Derived the OAuth `redirect_uri` from the per-request base URL in multi-host deployments ([#10127](https://github.com/better-auth/better-auth/pull/10127))
- Preserved previously granted `account.scope` values across re-authentication and token refresh ([#10128](https://github.com/better-auth/better-auth/pull/10128))
- Preserved the resolved OAuth user when `overrideUserInfo` returns `null` ([#10124](https://github.com/better-auth/better-auth/pull/10124))
- Fired session-delete hooks and revoked bound OAuth tokens for preserved sessions on `secondaryStorage` ([#9969](https://github.com/better-auth/better-auth/pull/9969))
- Issued SIWE nonces before the wallet address and chain ID are known ([#10234](https://github.com/better-auth/better-auth/pull/10234))
- Fixed client plugin composition so One Tap, Electron, and Expo type-check with `createAuthClient` ([#10505](https://github.com/better-auth/better-auth/pull/10505))
- Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication ([#9657](https://github.com/better-auth/better-auth/pull/9657))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/better-auth/CHANGELOG.md)
## `@better-auth/oauth-provider`
### ❗ Breaking Changes
- Moved the MCP plugin into its own `@better-auth/mcp` package built on the OAuth provider ([#9992](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9625))
- Added the `@better-auth/cimd` Client ID Metadata Document plugin ([#9159](https://github.com/better-auth/better-auth/pull/9159))
- Added the OAuth device authorization grant (RFC 8628) ([#10135](https://github.com/better-auth/better-auth/pull/10135))
- Added DPoP sender-constrained access tokens (RFC 9449) ([#10039](https://github.com/better-auth/better-auth/pull/10039))
- Added an extension surface for registering grants, client authentication methods, discovery metadata, and claim contributors ([#10030](https://github.com/better-auth/better-auth/pull/10030))
- Added a refresh token reuse interval that replays the same response for duplicate refresh requests ([#10145](https://github.com/better-auth/better-auth/pull/10145))
- Allowed confidential DCR clients to complete authorization-code flows without PKCE ([#10146](https://github.com/better-auth/better-auth/pull/10146))
- Added the `at_hash` claim to ID tokens issued alongside an access token, per OIDC Core §3.1.3.6 ([#9079](https://github.com/better-auth/better-auth/pull/9079))
- Made token introspection consistent across opaque and JWT tokens and scoped it to the audience ([#10045](https://github.com/better-auth/better-auth/pull/10045))
- Exposed the issuing `sessionId` to `id_token` claim contributors ([#10113](https://github.com/better-auth/better-auth/pull/10113))
- Honored requested UserInfo claims through a claim registry ([#10156](https://github.com/better-auth/better-auth/pull/10156))
- Removed the `silenceWarnings` option and the well-known endpoint warnings it suppressed ([#10703](https://github.com/better-auth/better-auth/pull/10703))
- Added protected dynamic client registration using RFC 7591 initial access tokens ([#10037](https://github.com/better-auth/better-auth/pull/10037))
- Added `private_key_jwt` client authentication for token endpoint requests (RFC 7523) ([#8836](https://github.com/better-auth/better-auth/pull/8836))
- Sent `Cache-Control: no-store` on every OAuth response that carries credentials ([#10065](https://github.com/better-auth/better-auth/pull/10065))
- Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers ([#9930](https://github.com/better-auth/better-auth/pull/9930))
### Bug Fixes
- Enforced RFC 8628 request cardinality and client authentication on the device flow endpoints ([#10752](https://github.com/better-auth/better-auth/pull/10752))
- Accepted the OpenID Provider issuer as the `aud` of `private_key_jwt` client assertions ([#10811](https://github.com/better-auth/better-auth/pull/10811))
- Accepted UserInfo bearer tokens sent in a form-encoded request body ([#10155](https://github.com/better-auth/better-auth/pull/10155))
- Allowed nonce-bound confidential clients to request `offline_access` without PKCE ([#10153](https://github.com/better-auth/better-auth/pull/10153))
- Returned a `401 invalid_token` challenge from `/oauth2/userinfo` for invalid tokens ([#10068](https://github.com/better-auth/better-auth/pull/10068))
- Completed the RP-initiated logout flow with form-encoded `POST` support and confirmation pages ([#10812](https://github.com/better-auth/better-auth/pull/10812))
- Deferred token revocation and back-channel logout delivery until the session deletion commits ([#10472](https://github.com/better-auth/better-auth/pull/10472))
- Accepted form-encoded POST authorization requests and rejected unsupported OIDC request objects ([#10151](https://github.com/better-auth/better-auth/pull/10151))
- Handled voluntary and essential `acr` claim requests per OIDC Core ([#10790](https://github.com/better-auth/better-auth/pull/10790))
- Kept `profile` and `email` scope claims on the UserInfo response instead of ID tokens ([#10152](https://github.com/better-auth/better-auth/pull/10152))
- Made the `private_key_jwt` `jti` single-use atomic across processes ([#9964](https://github.com/better-auth/better-auth/pull/9964))
- Made `redirect_uri` conditional at the token endpoint, required only when the authorization included one ([#10159](https://github.com/better-auth/better-auth/pull/10159))
- Preserved client key metadata and the requested authentication method during dynamic client registration ([#10144](https://github.com/better-auth/better-auth/pull/10144))
- Redirected authorization requests missing `response_type` to the verified client redirect URI ([#10149](https://github.com/better-auth/better-auth/pull/10149))
- Rejected authorization code replay with `invalid_grant` and revoked tokens issued from that code ([#10150](https://github.com/better-auth/better-auth/pull/10150))
- Reported `unsupported_token_type` when revoking a JWT access token ([#9970](https://github.com/better-auth/better-auth/pull/9970))
- Required the `openid` scope for authorization requests that use the `claims` parameter ([#10791](https://github.com/better-auth/better-auth/pull/10791))
- Returned `invalid_grant` when a client presents a refresh token issued to another client ([#10154](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9657))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/oauth-provider/CHANGELOG.md)
## `@better-auth/core`
### ❗ Breaking Changes
- Moved database joins out of `experimental` into the stable `advanced.database.joins` option ([#10359](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9898))
- Added per-provider `requireEmailVerification` for social sign-in ([#9929](https://github.com/better-auth/better-auth/pull/9929))
- Added a `user.validateUserInfo` gate for rejecting an identity before a user is created or linked ([#9864](https://github.com/better-auth/better-auth/pull/9864))
- Added compound table indexes to plugin database schemas ([#10402](https://github.com/better-auth/better-auth/pull/10402))
- Added `allowIdpInitiated` support for IdP-initiated flows through a secure server-side bounce ([#9301](https://github.com/better-auth/better-auth/pull/9301))
- Added RP-initiated logout so `signOut()` can also sign users out of the OpenID provider ([#9368](https://github.com/better-auth/better-auth/pull/9368))
- Added `refreshTokenParams` for forwarding extra parameters on generic OAuth token refresh ([#9948](https://github.com/better-auth/better-auth/pull/9948))
- Added an `includeGrantedScopes` option to the Google provider ([#10129](https://github.com/better-auth/better-auth/pull/10129))
- Added DPoP sender-constrained access tokens (RFC 9449) ([#10039](https://github.com/better-auth/better-auth/pull/10039))
- Added `private_key_jwt` client authentication for token endpoint requests (RFC 7523) ([#8836](https://github.com/better-auth/better-auth/pull/8836))
- Sent `Cache-Control: no-store` on every OAuth response that carries credentials ([#10065](https://github.com/better-auth/better-auth/pull/10065))
- Added per-request `additionalParams` and `loginHint` across `signIn.social`, `linkSocial`, and `signIn.sso` ([#9305](https://github.com/better-auth/better-auth/pull/9305))
- Added JWKS-backed asymmetric JWTs for the session cookie cache ([#8931](https://github.com/better-auth/better-auth/pull/8931))
- Added transactional OIDC user resolution for SSO sign-ins ([#10473](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10126))
- Derived the OAuth `redirect_uri` from the per-request base URL in multi-host deployments ([#10127](https://github.com/better-auth/better-auth/pull/10127))
- Preserved previously granted `account.scope` values across re-authentication and token refresh ([#10128](https://github.com/better-auth/better-auth/pull/10128))
- Fixed client plugin composition so One Tap, Electron, and Expo type-check with `createAuthClient` ([#10505](https://github.com/better-auth/better-auth/pull/10505))
- Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication ([#9657](https://github.com/better-auth/better-auth/pull/9657))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/core/CHANGELOG.md)
## `@better-auth/sso`
### ❗ Breaking Changes
- Scoped account identity by trusted issuer, keying accounts on `(issuer, accountId)` ([#10403](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9864))
- Added `allowIdpInitiated` support for IdP-initiated flows through a secure server-side bounce ([#9301](https://github.com/better-auth/better-auth/pull/9301))
- Added `private_key_jwt` client authentication for token endpoint requests (RFC 7523) ([#8836](https://github.com/better-auth/better-auth/pull/8836))
- Added per-request `additionalParams` and `loginHint` across `signIn.social`, `linkSocial`, and `signIn.sso` ([#9305](https://github.com/better-auth/better-auth/pull/9305))
- Added a server-trusted OAuth state channel, fixing anonymous account linking in in-app browsers ([#9930](https://github.com/better-auth/better-auth/pull/9930))
- Added transactional OIDC user resolution for SSO sign-ins ([#10473](https://github.com/better-auth/better-auth/pull/10473))
- Extended `resolveUser` to SAML sign-ins and hardened the provider lifecycle ([#10621](https://github.com/better-auth/better-auth/pull/10621))
- Added `additionalFields` support on `ssoProvider` ([#9445](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10072))
- Updated samlify to 2.13.1 for a signed-assertion XML injection fix ([#9821](https://github.com/better-auth/better-auth/pull/9821))
- Upgraded samlify to 2.12.0 with XPath injection and XXE fixes ([#9121](https://github.com/better-auth/better-auth/pull/9121))
- Single-sourced HTTP Basic credential encoding and decoding for OAuth client authentication ([#9657](https://github.com/better-auth/better-auth/pull/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`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/sso/CHANGELOG.md)
## `@better-auth/scim`
### ❗ Breaking Changes
- Decoupled SCIM provisioning from the organization plugin ([#10390](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9864))
- Added durable SCIM Group resources with connection-scoped membership and lifecycle endpoints ([#10018](https://github.com/better-auth/better-auth/pull/10018))
- Added the SCIM Enterprise User extension and the standard User attributes for interop conformance ([#10620](https://github.com/better-auth/better-auth/pull/10620))
- Added a managed connection catalog and runtime connection resolution ([#10592](https://github.com/better-auth/better-auth/pull/10592))
- Added `acquireActiveSCIMUserLink` for transaction-safe authentication of provisioned users ([#10474](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10682))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/scim/CHANGELOG.md)
## `@better-auth/mcp` ✨
### ❗ Breaking Changes
- Moved the MCP plugin into its own `@better-auth/mcp` package built on the OAuth provider ([#9992](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10039))
- Added a refresh token reuse interval that replays the same response for duplicate refresh requests ([#10145](https://github.com/better-auth/better-auth/pull/10145))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/mcp/CHANGELOG.md)
## `@better-auth/electron`
### ❗ Breaking Changes
- Enforced S256 PKCE in the Electron sign-in flow and hardened custom-scheme origin checks ([#9645](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10505))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/electron/CHANGELOG.md)
## `@better-auth/expo`
### ❗ Breaking Changes
- Switched Expo secure storage to async access so apps no longer crash when the iOS Keychain is unavailable ([#10438](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10505))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/expo/CHANGELOG.md)
## `@better-auth/stripe`
### ❗ Breaking Changes
- Made the `event` parameter of `onSubscriptionCancel` required ([#9531](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9359))
> **Migration:** `event` is always supplied, so drop `undefined` handling from the callback.
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/stripe/CHANGELOG.md)
## `auth`
### ❗ Breaking Changes
- Accumulated OAuth granted scopes in a new `grantedScopes` string array ([#9825](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/9547))
- Added compound table indexes to plugin database schemas ([#10402](https://github.com/better-auth/better-auth/pull/10402))
### Bug Fixes
- Preserved issuer-scoped account identities by restoring `Account.accountId` alongside the required `issuer` ([#10668](https://github.com/better-auth/better-auth/pull/10668))
- Exported the generated `pgSchema` binding so drizzle-kit can emit `CREATE SCHEMA` for custom PostgreSQL namespaces ([#10770](https://github.com/better-auth/better-auth/pull/10770))
- Loaded the auth config through c12 v4 `resolveModule`, adding support for the `export default { auth }` shape ([#9477](https://github.com/better-auth/better-auth/pull/9477))
- Reverted the granted scopes architecture, restoring the previous `account.scope` storage ([#10123](https://github.com/better-auth/better-auth/pull/10123))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/cli/CHANGELOG.md)
## `@better-auth/drizzle-adapter`
### ❗ Breaking Changes
- Moved database joins out of `experimental` into the stable `advanced.database.joins` option ([#10359](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/10402))
- Added a `relations-v2` entry point for projects using Drizzle Relations v2 ([#9489](https://github.com/better-auth/better-auth/pull/9489))
- Added a `schemaName` option that generates the Drizzle schema inside a `pgSchema` namespace ([#7169](https://github.com/better-auth/better-auth/pull/7169))
### Bug Fixes
- Exported the generated `pgSchema` binding so drizzle-kit can emit `CREATE SCHEMA` for custom PostgreSQL namespaces ([#10770](https://github.com/better-auth/better-auth/pull/10770))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/drizzle-adapter/CHANGELOG.md)
## `@better-auth/cimd` ✨
### ❗ Breaking Changes
- Aligned OAuth client registration and metadata with MCP authorization 2026-07-28 ([#10577](https://github.com/better-auth/better-auth/pull/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](https://github.com/better-auth/better-auth/pull/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`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/cimd/CHANGELOG.md)
## `@better-auth/api-key`
### ❗ Breaking Changes
- Hardened atomic state transitions so concurrent requests cannot race past single-use or rate-limit guards ([#10000](https://github.com/better-auth/better-auth/pull/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`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/api-key/CHANGELOG.md)
## `@better-auth/kysely-adapter`
### Bug Fixes
- Fixed SQLite dialect bundles on Kysely 0.29 by mirroring the migration-table constants locally ([#10377](https://github.com/better-auth/better-auth/pull/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`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/kysely-adapter/CHANGELOG.md)
## `@better-auth/i18n`
### Features
- Added built-in translations for 22 languages ([#9157](https://github.com/better-auth/better-auth/pull/9157))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/i18n/CHANGELOG.md)
## `@better-auth/mongo-adapter`
### Features
- Added compound table indexes to plugin database schemas ([#10402](https://github.com/better-auth/better-auth/pull/10402))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/mongo-adapter/CHANGELOG.md)
## `@better-auth/passkey`
### Features
- Added an optional `createSession` setting that signs the user in on successful passkey registration ([#9873](https://github.com/better-auth/better-auth/pull/9873))
For detailed changes, see [`CHANGELOG`](https://github.com/better-auth/better-auth/blob/c3688ba88edff12dfcb1ced007e332711509ac29/packages/passkey/CHANGELOG.md)
## 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`](https://github.com/better-auth/better-auth/compare/v1.6.30...v1.7.0)