v1.7.0
megadose/holehev1.7.0Aug 18, 2026by better-release[bot]
AI Summary
This is a major release featuring significant breaking changes including moving database joins to stable options, scoped account identity by trusted issuer, and separating the MCP plugin into its own package. It also adds OIDC back-channel logout, rewrites the OAuth plugin as a first-class social provider, and decouples SCIM provisioning from the organization plugin.
Key Highlights
- Moved database joins from experimental to stable `advanced.database.joins` option
- Scoped account identity by trusted issuer using `(issuer, accountId)`
- Moved MCP plugin into its own `@better-auth/mcp` package
- Added OIDC back-channel logout for ending sessions across connected apps
- Rewrote generic OAuth plugin as a first-class social provider with OAuth 2.1 defaults
Breaking Changes
- Moved database joins from experimental to stable `advanced.database.joins` option
- Scoped account identity by trusted issuer, keying accounts on `(issuer, accountId)`
- Moved the MCP plugin into its own `@better-auth/mcp` package
- Decoupled SCIM provisioning from the organization plugin
- Removed the deprecated `oidcProvider` plugin
New Features
- Added `clientAssertion` support to Microsoft Entra ID social provider
- Made the `Auth` instance directly fetchable
- Added per-provider `requireEmailVerification` for social sign-in
- Added `hydrateSession` for server-fetched data on first render
- Added compound table indexes to plugin database schemas
- Added `allowIdpInitiated` support for IdP-initiated flows
- Added RP-initiated logout support
- Verified discovery `id_tokens` against trusted verifiers
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)