chore(deps): update dependency better-auth to v1.7.0 #28

Open
renovatebot wants to merge 1 commit from renovate/all-minor-patch into main
Collaborator

This PR contains the following updates:

Package Type Update Change
better-auth (source) pnpm.catalog.default minor 1.6.291.7.0

Release Notes

better-auth/better-auth (better-auth)

v1.7.0

Minor Changes
  • #​8733 4e8e4c7 Thanks @​bytaesu! - Add hydrateSession to seed the client with a server-fetched session so useSession returns data on the first render.

  • #​9930 0cbaf81 Thanks @​gustavovalverde! - Anonymous account linking now works after social and generic OAuth sign-in in Expo and other in-app browsers, where the OAuth callback returns without the session cookie. onLinkAccount fires and the anonymous user is migrated; before, it was silently skipped.

    Plugins can now carry server-trusted data across an OAuth redirect with the new addOAuthServerContext API, read back on the callback via getOAuthState().serverContext. Unlike additionalData, it cannot be set from the request body, so it is the right place for values the server must trust.

    For @better-auth/oauth-provider, the post-login authorization query now travels through that server-only channel, so it can no longer be injected through additionalData.

  • #​10004 b36c38f Thanks @​bytaesu! - The captcha plugin now requires endpoint entries to match full auth paths unless they use wildcard patterns. This prevents requests like /sign-in//email from bypassing captcha while preserving trailing-slash matches like /sign-in/email/. To protect multiple routes, replace partial paths like /sign-in with explicit wildcards such as /sign-in/* or /sign-in/**.

  • #​10746 6782647 Thanks @​gustavovalverde! - OAuth device grants now use oauthDeviceAuthorization() alongside oauthProvider() or mcp(). This single integration replaces both the standalone deviceCodeGrant() plugin and the shared-grant configuration. Standalone Device Authorization no longer accepts or stores RFC 8707 resources, and onDeviceAuthRequest receives only clientId and scope. The OAuth integration rejects resource indicators that are not absolute, fragment-free URIs.

    The OAuth integration replaces the optional resource column with oauthClientId and resources. Regenerate and apply the schema when using it. Before upgrading from an earlier 1.7 prerelease, let pending OAuth device codes expire or delete them because they cannot be exchanged through the new integration.

  • #​10402 763a267 Thanks @​gustavovalverde! - Plugin database schemas can now define named or generated table-level indexes across multiple fields. SQL migrations and generated Drizzle or Prisma schemas resolve configured table and column names consistently, while the MongoDB adapter creates the same indexes before the first index-enforcing write.

  • #​9766 bf39cbf Thanks @​GautamBytes! - Add a server-only auth.api.consumePhoneNumberOTP API for custom phone OTP flows that need to verify and consume a code without creating or updating users or sessions.

  • #​10330 081d3c3 Thanks @​ping-maxwell! - Allow the username plugin's separate displayUsername field to be omitted by
    setting displayUsername: false on both the server and client plugins.

  • #​10059 49b5cf6 Thanks @​GautamBytes! - Device Authorization now creates unique database indexes for deviceCode and userCode, so each generated code must be unique in its column. Existing installations on every adapter must resolve duplicate values before applying the migration. MySQL and SQL Server installations must also convert both columns to bounded strings and clean up values longer than 191 characters before running it.

    Generated codes are limited to 191 characters. Issuance makes up to 3 attempts to overcome unique-key collisions, then returns server_error if it cannot create a unique deviceCode and userCode. Default-generated user codes accept case changes and readability separators during verification, approval, and denial; custom codes outside the default alphabet are matched exactly. The /device limiter allows 5 requests over a window equal to the configured code lifetime, while /device/token polling keeps its separate interval behavior.

  • #​9645 e014029 Thanks @​ping-maxwell! - Harden the Electron OAuth flow and tighten custom-scheme trusted-origin matching.

    The Electron sign-in flow now mandates PKCE S256. Plain PKCE is rejected: the code_challenge_method parameter is gone and every authorization code is verified by hashing the verifier with SHA-256. The server no longer trusts an electron-origin header to set the request Origin. The Electron client now sends a real Origin (for example myapp:/), so upgrade the @better-auth/electron client and server together and make sure your app's scheme is in trustedOrigins. The unused disableOriginOverride option is removed.

    Custom-scheme entries in trustedOrigins now match by scheme and authority instead of string prefix. A host-less entry such as myapp:// or exp:// still trusts every host of that scheme, but a host-bearing entry such as myapp://callback matches that host exactly, so it is no longer satisfied by myapp://callback.attacker.tld.

  • #​9948 3d04fab Thanks @​yordis! - feat(generic-oauth): add refreshTokenParams config to forward extra params on token refresh

    Multi-tenant OIDC providers (Zitadel multi-org, Auth0 with audience) need to send extra body params on the refresh call to rescope tokens without a full authorization redirect. The generic-oauth plugin now accepts a refreshTokenParams option (object or sync/async function) that is merged into the refresh request body, with grant_type and refresh_token protected from override. The function form receives request metadata for the request that triggered the refresh, so request-scoped data (headers, cookies) is available without out-of-band state like AsyncLocalStorage.

    UpstreamProvider.refreshAccessToken now accepts an optional second ctx argument; the change is backwards compatible because existing implementations that take only refreshToken remain valid. See #​7554.

  • #​9069 c7d2253 Thanks @​gustavovalverde! - Rewrite the generic OAuth plugin as a first-class social provider with OAuth 2.1 security defaults. Providers now use signIn.social + callback/:id instead of dedicated plugin endpoints, with PKCE required by default (OAuth 2.1), RFC 9207 issuer validation, OIDC auto-discovery with openid scope injection, and typed provider IDs.

    Breaking changes:

    • signIn.oauth2({ providerId }) replaced by signIn.social({ provider })
    • oauth2.link() replaced by linkSocial()
    • Callback URL changed from /api/auth/oauth2/callback/:id to /api/auth/callback/:id
    • genericOAuthClient() removed; generic OAuth providers now use the standard social client APIs
    • pkce defaults to true (was false); set pkce: false for providers that reject PKCE
    • authorizationUrlParams and tokenUrlParams only accept Record<string, string>
    • issuer and requireIssuerValidation config fields removed; issuer validation is automatic via OIDC discovery
    • mapProfileToUser profile typed as OAuth2UserInfo & Record<string, unknown>
  • #​9966 ec8a38c Thanks @​gustavovalverde! - genericOAuth providers configured with a discoveryUrl now verify the provider's id_token against its published JWKS (signature, issuer, audience, and advertised algorithms) and bind it to the authorization request with a server-generated OIDC nonce. A sign-in whose id_token fails verification, or does not echo the expected nonce, is rejected.

    Set disableIdTokenNonceBinding: true on a provider that does not return the nonce claim in the authorization-code flow.

    These providers also accept client-submitted id_token sign-in through signIn.social({ idToken }), which previously returned ID_TOKEN_NOT_SUPPORTED.

    Providers configured with explicit endpoints instead of discoveryUrl are unchanged.

  • #​9368 430c895 Thanks @​GautamBytes! - Generic OAuth users can now sign out from the configured OpenID provider when they call authClient.signOut(). When a provider exposes a discovered or configured logout endpoint, Better Auth redirects to it and includes the stored id_token_hint when available. Pass callbackURL or configure postLogoutRedirectURI for the return flow, with optional state, or set disableRedirect to handle the returned url yourself. When multiple linked providers support logout, Better Auth selects the most recently updated account. Set disableProviderLogout: true to keep sign-out local.

  • #​9431 523f95c Thanks @​pi0! - feat: make Auth instance fetchable

  • #​10577 5c45abc Thanks @​gustavovalverde! - 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 WWW-Authenticate challenge that names every missing scope. Clients can union those scopes into one authorization request instead of opening one browser redirect per scope.

    • Configure protected scopes with requiredScopes through RequireMcpAuthOptions or the matching createMcpProtectedRequestHandler verifier option. Exact membership remains the default; isScopeSatisfied can define hierarchical policies.
    • Use createInsufficientScopeError when an operation determines its required scopes dynamically. createResourceServerChallenge converts that signal and recognized token failures into safe RFC 6750 challenges.
    • Use challengeScopes only as the unauthenticated challenge hint.

    Handler-produced responses, ordinary permission denials, configuration failures, and unrelated thrown values keep their original status and identity.

  • #​10403 dbd302e Thanks @​gustavovalverde! - Scope account identity by trusted issuer instead of provider configuration. Accounts now use the unique (issuer, accountId) key, so aliases for one OpenID Connect issuer deduplicate one external identity while equal subjects from different issuers remain separate. This identity deduplication does not introduce independent grant or provider lifecycle records for aliases.

    This release requires Account.issuer but preserves Account.accountId as the provider-assigned account identifier. Account-specific APIs select the local Account.id through the accountId request property; token and provider-profile APIs can instead select the signed account cookie with useAccountCookie: true. Credential accounts use local:credential and the linked user's stable id as their provider identity.

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

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

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

  • #​10359 8784c1c Thanks @​ping-maxwell! - Database joins have moved out of experimental into a stable option at advanced.database.joins (default: false).

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

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

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

  • #​9992 e53582c Thanks @​gustavovalverde! - The MCP plugin moves out of better-auth into its own package, @better-auth/mcp, built on @better-auth/oauth-provider. Import the authorization plugin and protected-request helpers from the package root. The in-core MCP client (createMcpAuthClient and its adapters) is removed; MCP protocol and transport clients come from the official version 2 @modelcontextprotocol/client and @modelcontextprotocol/server packages. The OAuth endpoints move from /mcp/* to /oauth2/*, with discovery at /.well-known/oauth-authorization-server and protected resource metadata at /.well-known/oauth-protected-resource. Discovery-based MCP clients pick up the new locations on their own.

    The shared-auth route helper is renamed from withMcpAuth to requireMcpAuth. The standalone protected-resource factory is renamed from mcpHandler to createMcpProtectedRequestHandler; pass one flat McpProtectedRequestHandlerOptions object with issuer, a single audience, optional jwtVerifyOptions, token-verification fields, and challenge fields. Its callback receives accessTokenClaims. requireMcpAuth verifies the access token against the published JWKS, validates DPoP proofs for DPoP-bound tokens, and passes the verified access-token claims to your handler.

    createInsufficientScopeError now validates a custom description against the RFC 6750 error_description character set when the error is constructed. Invalid descriptions throw TypeError("invalid error_description") before an error can reach resource-challenge serialization.

    MCP 2026-07-28 uses a stateless request and response transport. Serve MCP routes with version 2 of @modelcontextprotocol/server, configure createMcpHandler with legacy: "reject", wrap it with requireMcpAuth, and export only POST. Remove MCP-route GET and DELETE exports and session-store options such as redisUrl. OAuth clients, consent, authorization codes, refresh tokens, and security records remain durable authorization state.

    To migrate, install @better-auth/mcp, @better-auth/cimd, and the official version 2 MCP client or server package needed by your application; add the jwt() plugin, which is now required for token signing; and move options that were nested under oidcConfig to flat options on mcp({ ... }). The database models change: oauthApplication becomes oauthClient, with new oauthRefreshToken and oauthClientAssertion tables. Regenerate or migrate your schema with npx auth migrate or npx auth generate.

  • #​10204 0683a5f Thanks @​GautamBytes! - Microsoft sign-in now identifies Entra accounts with the stable oid claim in both the built-in microsoft provider and the Generic OAuth microsoftEntraId helper. Tokens without a valid oid are rejected, and the Generic OAuth helper refuses to initialize unless Microsoft discovery provides ID-token verification metadata. Existing Microsoft account rows created from sub must be migrated before upgrading.

  • #​9305 e7eb45b Thanks @​gustavovalverde! - feat(oauth): per-request additionalParams and loginHint parity across signIn.social, linkSocial, and signIn.sso

    Unified escape hatch for customizing the provider authorization URL on a per-request basis. Previously, dynamic parameters like Google's access_type=offline / prompt=consent, Cognito's identity_provider=Google, or Microsoft's domain_hint could only be set as static server configuration.

New capabilities
  • signIn.social, linkSocial, and signIn.sso accept additionalParams: Record<string, string>. Values are appended to the authorization URL as query parameters.
  • linkSocial also accepts loginHint, matching the surface of signIn.social and signIn.sso.
  • OAuthProvider.createAuthorizationURL gains additionalParams in its input contract; every built-in provider forwards it to the shared helper.
  • Generic-OAuth providers merge call-time additionalParams with the config-level authorizationUrlParams; call-time wins on key collision.
  • Cognito exposes a typed identityProvider?: string config option that maps to the identity_provider query parameter, avoiding magic strings.
Security
  • The shared createAuthorizationURL helper silently drops any caller-supplied key in RESERVED_AUTHORIZATION_PARAMS (state, client_id, redirect_uri, response_type, code_challenge, code_challenge_method, nonce, scope). The request-body Zod schema rejects the same keys with 400, so misuse is visible at the edge rather than silently overriding security-critical parameters. nonce is reserved so a caller cannot replace the OIDC nonce Better Auth generates when binding a discovery provider's id_token to the authorization request.
  • Providers that use non-standard client identifiers (wechatappid, tiktokclient_key) additionally filter those keys so a caller cannot swap the configured OAuth app.
  • Provider protocol constants that are required for the integration to function (atlassianaudience, notionowner) are merged last so caller-supplied additionalParams cannot override them. Configured defaults that represent operator intent (e.g. Google include_granted_scopes, Cognito identityProvider) remain caller-overridable.
  • signIn.sso rejects additionalParams with 400 when the resolved provider is SAML; the SAML AuthnRequest is signed and cannot carry caller-supplied query parameters, so silently dropping them would mislead integrators.
OpenAPI
  • Added ZodRecord handling to the OpenAPI generator so z.record() fields emit type: object with typed additionalProperties. Incidentally fixes a long-standing bug where additionalData was rendered as type: string.
Refactors
  • discord, roblox, zoom, and slack providers now delegate to the shared createAuthorizationURL helper and inherit its RFC behavior and reserved-key guard.
  • tiktok and wechat keep their manual URL construction (non-standard OAuth2 parameter names and URL fragment requirements) but thread additionalParams with the same reserved-key filter.

Closes #​2351.
Closes #​5441.
Closes #​5592.
Closes #​5604.
Supersedes #​4992 and #​5443.

  • #​10127 7c7313c Thanks @​gustavovalverde! - OAuth sign-in, account linking, callback, and proxy flows now build redirect_uri from the current request base URL when baseURL.allowedHosts is configured. Built-in social providers and generic OAuth providers now use the resolved request host for redirects in multi-host deployments.

    Custom OAuthProvider implementations can omit callbackPath when using the shared /callback/<provider-id> route. Set callbackPath only for custom callback routes.

  • #​10039 aedcb97 Thanks @​gustavovalverde! - feat(oauth-provider)!: DPoP-bound access tokens (RFC 9449)

    OAuth provider integrations can issue and verify DPoP sender-constrained tokens. Clients request them with dpop_bound_access_tokens at registration, dpop_jkt on the authorization request, or by targeting a resource configured with dpopBoundAccessTokensRequired. Issued tokens carry cnf.jkt, return token_type: "DPoP", and stay bound through refresh-token rotation, introspection, and userinfo.

    Resource servers verify DPoP requests with verifyAccessTokenRequest, which checks the Authorization: DPoP scheme, the proof, the request target, the access-token hash, and proof replay. The MCP package advertises DPoP in protected resource metadata and verifies DPoP-bound requests. Proof replay is rejected through the database-backed verification store, so anti-replay holds across instances. verifyAccessTokenRequest and requireMcpAuth use that store by default; build one with createDpopReplayStore(internalAdapter) or pass a custom dpop.replayStore. This needs database-backed verification storage: a secondary-storage-only deployment rejects DPoP requests rather than skipping replay protection.

    Breaking: the raw-token verifier verifyAccessToken is renamed to verifyBearerToken, both in better-auth/oauth2 and as the oauthProviderResourceClient action, and it rejects DPoP-bound tokens. Use verifyAccessTokenRequest on any endpoint that may receive them. The resource-request input type is renamed from AccessTokenRequestInput to ResourceRequestInput, and the DPoP algorithm option is signingAlgorithms everywhere.

    Run a schema migration for the DPoP token-binding fields: the confirmation column on the access-token and refresh-token tables. DPoP-bound clients also gain dpopBoundAccessTokens and resources dpopBoundAccessTokensRequired. No dedicated replay table is added; proof replay reuses the verification store.

  • #​9828 4f53b61 Thanks @​gustavovalverde! - Verify social-provider id_tokens with a single shared verifier.

    Client-submitted id_token sign-in (signIn.social({ idToken }) and account linking) is verified by one function instead of a per-provider verifyIdToken method. Each provider declares an idToken config with a JWKS source, issuer, and audience, and the core verifier runs the signature, issuer, audience, and nonce checks. A provider that declares no config rejects the client id_token path.

    PayPal previously accepted any decodable id_token without verifying its signature. PayPal derives identity from the access token, so it now declares no idToken config, and the client id_token path returns ID_TOKEN_NOT_SUPPORTED. PayPal sign-in through the redirect flow is unchanged.

    Custom providers that implement UpstreamProvider directly replace the removed verifyIdToken method with an idToken config:

    idToken: {
    	jwks: createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")),
    	issuer: "https://issuer.example",
    	audience: clientId,
    },
    

    For verification that cannot use a local JWKS, pass idToken: { verify: async (token, nonce) => boolean }. The verifyIdToken and disableIdTokenSignIn provider options are unchanged.

  • #​9079 6f2948e Thanks @​gustavovalverde! - feat(oauth-provider): compute at_hash in ID tokens per OIDC Core §3.1.3.6

    ID tokens issued alongside an access token now include the at_hash claim, which cryptographically binds the two tokens to prevent token substitution attacks. The hash algorithm is selected based on the actual signing key's algorithm (EdDSA/Ed25519 uses SHA-512, RS/ES/PS384 uses SHA-384, RS/ES/PS512 uses SHA-512, all others use SHA-256).

    A new resolveSigningKey() export is available from better-auth/plugins to resolve the current JWKS signing key (including its algorithm). When using a custom jwt.sign callback, the signed ID token's header is validated against the declared algorithm to prevent at_hash mismatches.

  • #​10135 f68044d Thanks @​brentmitchell25! - Registered OAuth clients can now use the RFC 8628 device flow to obtain OAuth access tokens. Add oauthDeviceAuthorization() alongside oauthProvider() or mcp(), request a code at /device/code, and exchange it at /oauth2/token after the user approves it. OAuth and OpenID discovery advertise the device_authorization_endpoint.

    Device authorization requests can bind RFC 8707 resource indicators. GET /device returns the requested client, scopes, and resources to the authenticated user who owns the request. Token requests can reuse or narrow the approved resources, but cannot add new ones. Existing first-party device clients continue to receive Better Auth session tokens from /device/token.

    Enabling oauthDeviceAuthorization() adds nullable oauthClientId and resources fields to deviceCode. Regenerate and apply the database schema after adding the integration.

    Confidential clients authenticate at /device/code with their registered method, while public clients send client_id. Empty client_id, scope, user_id, and authentication values are treated as omitted; multiple non-empty values for any of these parameters return invalid_request, while multiple resource values remain supported. Unknown OAuth client IDs enter the standalone device flow only when oauthDeviceAuthorization({ validateClient }) accepts them.

  • #​9929 91f235f Thanks @​gustavovalverde! - Add requireEmailVerification to OAuth provider options, for built-in social providers and the Generic OAuth plugin. When a provider reports an unverified email, the user and account are still created or linked, but no session is issued: the OAuth callback redirects with ?error=email_not_verified, and ID token and One Tap sign-in return 403 EMAIL_NOT_VERIFIED. Verification emails follow the existing emailVerification.sendOnSignUp / sendOnSignIn settings.

    It is opt-in per provider and does not inherit emailAndPassword.requireEmailVerification, so existing social logins keep working. The gate checks the local user's verification state, so a user verified through another method keeps access. Only enable it for providers that report a trustworthy email_verified signal.

  • #​9648 d2a79ba Thanks @​brentmitchell25! - OAuth provider now models protected resources explicitly. Configure them with resources or create them through the oauthResource admin API. Each resource can define token TTLs, allowed scopes, custom JWT claims, and JWT signing pins.

    validAudiences is removed. Move each existing resource identifier into resources; link clients that should be limited to specific resources through oauthClientResource or Dynamic Client Registration resources.

    Access-token issuance now applies resource policy to the requested RFC 8707 resource values. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emits jti, and keeps repeated resource form parameters.

    Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource refreshTokenTtl longer than refreshTokenExpiresIn will see refresh tokens expire at the provider default instead of the longer resource value.

    JWT signing can now honor per-resource pins. signJWT() accepts signingKeyId and signingAlgorithm; JWKS adapters expose getKeyById() and getLatestKeyByAlg(). The jwks table adds nullable alg and crv columns, and keyPairConfigs can provision multiple algorithms in one keyring.

    After upgrading, run npx auth generate and apply the migration before deploying. The migration adds oauthResource, oauthClientResource, and the new jwks columns. Without it, resources using signingAlgorithm cannot find matching keys.

    Resource servers should publish RFC 9728 protected-resource metadata at their own origin. The OAuth provider exposes challenge helpers that point clients at that metadata.

    @better-auth/mcp now requires an explicit resource option. The plugin stores that identifier as an OAuth resource, publishes RFC 9728 protected-resource metadata for it, and binds issued access tokens to that resource. Existing mcp({ loginPage, consentPage }) setups should add a protected MCP resource identifier, for example resource: "https://api.example.com/mcp".

  • #​10397 bb6c102 Thanks @​ping-maxwell! - Add organization.getOrganization() to fetch organization metadata without members or invitations.

  • #​8931 34558bc Thanks @​GautamBytes! - Add opt-in JWKS-backed asymmetric JWT support for session_data cookie cache tokens, so services can verify cookie-cache JWTs with public keys instead of shared secrets. Enable it with jwt({ sessionCookieCache: true }) alongside session.cookieCache.strategy = "jwt".

  • #​8977 954b664 Thanks @​ruban-s! - allow passing userId and organizationId to the listUserTeams API. userId lets callers list teams for another member of an organization (gated behind the member:update permission). organizationId scopes the result to a specific organization without needing to switch the session's active organization, matching the pattern used by addTeamMember/removeTeamMember.

  • #​9969 76a3342 Thanks @​gustavovalverde! - Signing out with secondaryStorage and session.preserveSessionInDatabase now runs your configured session.delete hooks and marks the preserved session row as ended. OAuth Provider access and refresh tokens bound to that session are revoked and back-channel logout is dispatched on sign-out. Previously these hooks were skipped in this setup, so the tokens stayed valid until they expired.

  • #​9657 1e5b808 Thanks @​gustavovalverde! - Harden private_key_jwt and token endpoint client authentication, and add the helpers that make the fix structural.

    @better-auth/core/oauth2 now exposes encodeBasicCredentials and decodeBasicCredentials, a round-trip-tested pair that follows RFC 6749 §2.3.1 (application/x-www-form-urlencoded each value, split on the first : only). The decoder accepts the scheme case-insensitively and tolerates one or more spaces before the credentials per RFC 7235 §2.1. client_secret_basic on the client side and the Better Auth OAuth provider on the server side both go through these helpers, so credentials containing reserved characters round-trip cleanly across the stack and headers like basic xxx or Basic xxx are accepted.

    createPrivateKeyJwtClientAssertionGetter validates options eagerly. Unsupported algorithms (HS256, none), a JWK with no key material, and disagreement between an explicit algorithm and the JWK-embedded alg all throw at construction rather than on the first token request. signPrivateKeyJwtClientAssertion enforces the same checks for direct callers. Breaking: configurations that paired an unsupported JWK alg with a different explicit algorithm used to silently sign with the explicit option; they now fail at construction.

    Breaking: @better-auth/oauth-provider accepts client jwks metadata only as an RFC 7517 JWK Set object with a non-empty keys array. Replace jwks: [key] with jwks: { keys: [key] } in DCR payloads, administrative and user client creation, Client ID Metadata Documents, test fixtures, and generated client code. Remotely fetched jwks_uri responses must use the same object shape. EC keys must use P-256, P-384, or P-521; OKP keys must use Ed25519. When a key declares alg, it must be a supported private_key_jwt algorithm that matches the key type and curve; omit alg when the client chooses the algorithm in its assertion header. OAuth client rows previously written through oauthToSchema are already stored as JWK Set objects, so this is a request, configuration, and type migration rather than another database rewrite; audit rows written outside Better Auth separately.

    The SSO private_key_jwt flow redirects with error_description=no_private_key_available when a resolvePrivateKey callback returns no privateKeyJwk or privateKeyPem. The redirect path previously short-circuited only when the resolver was absent entirely; an empty resolver return fell through into an internal signing error.

    better-auth/test adds getHttpTestInstance, a counterpart to getTestInstance that binds a real HTTP listener on an OS-assigned port and constructs the auth instance against the discovered URL. It removes the temp-server-then-rebind race that test files have been individually copy-pasting.

  • #​8836 93d3871 Thanks @​gustavovalverde! - Add client authentication configuration for token endpoint requests across the stack, including private_key_jwt (RFC 7523).

    Generic OAuth providers now accept tokenEndpointAuth for token endpoint client authentication. Use tokenEndpointAuth: { method: "private_key_jwt", getClientAssertion } for JWT client assertions, { method: "none" } for public clients, and { method: "client_secret_basic" } or { method: "client_secret_post" } with clientSecret for explicit secret-based client authentication. The existing authentication: "basic" | "post" option remains available for secret-based token requests.

    Use createPrivateKeyJwtClientAssertionGetter() to sign RFC 7523 assertions from a private key. The assertion getter receives { clientId, tokenEndpoint, grantType }, so integrations do not duplicate client ID or token endpoint values inside assertion helpers. Core OAuth2 now exports private-key JWT-specific helpers and types: signPrivateKeyJwtClientAssertion, createPrivateKeyJwtClientAssertionGetter, PrivateKeyJwtSigningAlgorithm, and PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.

    Token endpoint client authentication parameters are derived from clientId, clientSecret, and tokenEndpointAuth. Configured token endpoint authentication requires clientId; secret-based token endpoint authentication also requires clientSecret. Custom token parameters are for provider-specific fields and do not replace the configured client authentication values.

    refreshAccessToken() now forwards resource values to refresh-token requests, so RFC 8707 resource indicators work through both the high-level refresh helper and refreshAccessTokenRequest().

    The synchronous OAuth2 request builders createAuthorizationCodeRequest, createRefreshAccessTokenRequest, and createClientCredentialsTokenRequest have been removed. Use the async authorizationCodeRequest, refreshAccessTokenRequest, and clientCredentialsTokenRequest helpers instead.

    Servers verify JWT client assertions signed with asymmetric keys, and clients can use the same token endpoint authentication contract for authorization code, refresh, and client credentials token requests.

  • #​9134 652fa53 Thanks @​gustavovalverde! - The dynamic baseURL config now ignores x-forwarded-host and x-forwarded-proto unless you set advanced.trustedProxyHeaders: true.

    Requests using baseURL: { allowedHosts } now resolve the auth origin from Host by default, so forwarded headers cannot select another allowed host unless trusted proxy headers are enabled.

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

    Migration:

    betterAuth({
      baseURL: { allowedHosts: [...] },
      advanced: {
        trustedProxyHeaders: true,
      },
    });
    
  • #​9240 729c00d Thanks @​adrianmxb! - feat(username): add immutable username option

    This allows users to set their username during sign-up or first update, but prevents changing it to a different value afterwards. Users can still update other profile fields.

  • #​10031 6fe9faa Thanks @​gustavovalverde! - Remove the deprecated oidcProvider plugin from better-auth/plugins. Migrate OIDC authorization-server integrations to @better-auth/oauth-provider.

  • #​10473 ed61b47 Thanks @​gustavovalverde! - Add transactional OIDC user resolution so applications can link verified issuer and subject pairs to exact existing users while preserving or updating the local profile.

  • #​10234 973fdde Thanks @​gustavovalverde! - The SIWE plugin now issues nonces before the wallet address or Chain ID is known. authClient.siwe.nonce() and authClient.siwe.getNonce() no longer accept wallet fields, getNonce must return an ERC-4361 nonce (8-250 alphanumeric characters), and SIWE verification now reads the wallet address and Chain ID from the signed ERC-4361 message.

  • #​9864 41cca60 Thanks @​GautamBytes! - Add a user.validateUserInfo provisioning gate that lets applications reject an identity before a user is created or a new account is linked. It runs once at the creation step for every method that provisions a user (OAuth, SSO/SAML, email/password, magic link, email OTP, anonymous, SIWE, phone number, admin-created users, and SCIM), including stateless setups with no persistent database.

    It also re-runs when an existing OAuth or SSO user signs in again (source.action is "sign-in"), where it receives the fresh provider email and profile so a domain or org policy can reject a user whose provider identity moved out of bounds. Non-provider returning sign-ins are not re-validated.

    The callback receives the mapped user plus a source describing the action (create-user, link-account, or sign-in), the method, and provider metadata: source.oauth for OAuth providers and source.sso for OIDC/SAML SSO providers. Return { error, errorDescription } to reject: browser flows redirect to the error URL and programmatic flows return a 403.

  • #​10036 ad35ead Thanks @​bytaesu! - Require Google One Tap server callbacks to resolve a Google client ID before verifying ID tokens. Configure oneTap({ clientId }) or socialProviders.google.clientId when using the One Tap plugin.

  • #​9057 544f1c6 Thanks @​gustavovalverde! - feat(two-factor)!: add OTP-only enablement and a discriminated response

    enableTwoFactor now accepts a method parameter ("otp" | "totp", default "totp") and returns a discriminated response with a method field.

method: "otp"
  • Sets twoFactorEnabled: true immediately.
  • Returns { method: "otp" }.
  • Requires otpOptions.sendOTP to be configured on the server; rejects with OTP_NOT_CONFIGURED otherwise.
method: "totp" (default)
  • Returns { method: "totp", totpURI, backupCodes }.
  • Rejects with TOTP_NOT_CONFIGURED if totpOptions.disable is set.

The existing skipVerificationOnEnable option remains supported for TOTP enrollment.

Breaking changes
  • Response shape changed: enableTwoFactor includes a method field in the response ("otp" or "totp").
Patch Changes
  • #​10014 73541c1 Thanks @​gustavovalverde! - Cloudflare Workers apps can now start when importing Better Auth subpaths such as better-auth/db. Beta builds were crashing during module initialization before application code ran.

  • #​10299 cf8eaac Thanks @​momomuchu! - widen drizzle-kit peer dependency range

  • #​10501 65fc17c Thanks @​KingIronMan2011! - Expand the optional drizzle-orm peer range to ^0.45.2 || >=1.0.0-rc.1 <2.0.0, matching @better-auth/drizzle-adapter and allowing Drizzle ORM v1 RC installations without peer dependency warnings.

  • #​10622 ecd83da Thanks @​gustavovalverde! - Sign-up no longer deadlocks when session cookie caching uses the JWT strategy on a single-connection SQLite database with native transactions enabled. JWKS key lookups and creation now resolve the transaction-scoped adapter instead of always querying the root connection, so minting a signing key during sign-up joins the surrounding transaction instead of racing it for the only available connection. On multi-connection databases (Postgres, MySQL) this also fixes a silent atomicity gap where a JWKS key created mid-transaction could commit independently of the transaction it was minted in.

  • #​10293 fe4c820 Thanks @​gustavovalverde! - npx auth migrate can now add required columns with static defaults and nullable
    unique columns to existing SQLite, PostgreSQL, and MySQL tables. Required unique
    columns still need distinct values to be backfilled manually before applying the
    unique constraint.

  • #​9898 7fe0e2b Thanks @​ItalyPaleAle! - Add clientAssertion support to the Microsoft Entra ID social provider.

  • #​9301 03e6c94 Thanks @​gustavovalverde! - Add allowIdpInitiated to GenericOAuthConfig and SSO OIDCConfig to support providers that initiate OAuth without a state parameter (e.g. Clever). When enabled, stateless callbacks restart the OAuth flow server-side with fresh state and PKCE, preserving CSRF protection. Also hardens parseState against undefined request bodies on GET callbacks.

  • #​10065 2196ea6 Thanks @​gustavovalverde! - OAuth and device-authorization responses that carry credentials now consistently send Cache-Control: no-store and Pragma: no-cache, so proxies, CDNs, and browsers never cache them. This covers the token, introspection, and userinfo endpoints, dynamic and admin client registration, client secret rotation, and the device code and device token responses, including the error responses from those endpoints.

    Endpoints declare this with metadata: { noStore: true }, and the header set is exported from @better-auth/core as NO_STORE_HEADERS for responses built by hand.

  • #​10124 06daf70 Thanks @​gustavovalverde! - Preserve the resolved OAuth user when overrideUserInfo returns null during account linking.

  • #​9304 e0d2b9e Thanks @​gustavovalverde! - Propagate sign-out to every connected app and cut off API access immediately, via OIDC Back-Channel Logout 1.0.

    When a user's session ends at the OP (sign-out, /oauth2/end-session, admin revoke, ban), @better-auth/oauth-provider now notifies every Relying Party that holds tokens for that session. The user's API access is cut off right away, instead of access tokens staying usable until their own TTL. Each client opts in by registering a backchannel_logout_uri (and optionally backchannel_logout_session_required) via DCR or the admin client-create endpoint. The provider signs a logout+jwt Logout Token per client and POSTs it to that client in parallel, with a short per-RP timeout.

    Breaking change. Introspection of an opaque or JWT access token whose bound session has ended now returns { active: false }, and /oauth2/userinfo rejects it with invalid_token. Previously the token stayed active until its own TTL. If you relied on access tokens outliving the user's session, that no longer holds.

    Refresh tokens without offline_access are revoked on session end; offline_access refresh tokens are preserved so long-lived API access can survive the browser session (OIDC Back-Channel Logout 1.0 §2.7). Access-token invalidation on session end is an additional OP hardening choice beyond §2.7, enforced by session liveness, so it holds even when the JWT plugin is disabled.

    Delivery runs through the host's background task handler when one is configured (Vercel waitUntil, Cloudflare ctx.waitUntil); without a handler it completes inline so notifications are not lost on request teardown. Configure advanced.backgroundTasks.handler on serverless runtimes to keep sign-out fast.

    Discovery at /.well-known/openid-configuration and /.well-known/oauth-authorization-server advertises backchannel_logout_supported: true and backchannel_logout_session_supported: true when the JWT plugin is enabled. Every registered backchannel_logout_uri must be a credential-free public HTTPS URL without a fragment; loopback HTTP is rejected for both public and confidential clients. CIMD documents cannot register back-channel logout metadata. The SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, also covers a private_key_jwt client's jwks_uri.

    Schema changes on @better-auth/oauth-provider:

    • oauthClient.backchannelLogoutUri: string | null
    • oauthClient.backchannelLogoutSessionRequired: boolean
    • oauthAccessToken.revoked: Date | null

    better-auth's signJWT gains an optional header argument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such as typ: "logout+jwt", can now set it without reaching for the low-level signing primitives.

  • #​10125 a83152e Thanks @​gustavovalverde! - Create new OAuth accounts in the user creation transaction. Adapters with native
    transaction support roll back the user when the account write fails, while other
    adapters still perform the writes sequentially.

  • #​10128 97903c9 Thanks @​gustavovalverde! - Preserve previously granted OAuth scopes across sign-in re-authentication and refresh-token requests. account.scope now accumulates monotonically: newly granted scopes are merged in only when added via linkSocial, and providers returning a narrower scope claim than the user has granted no longer shrink the stored value.

  • #​10170 6ddb555 Thanks @​gustavovalverde! - Bundled dependencies were refreshed to their latest compatible releases, including jose, nanostores, the noble crypto packages, and SimpleWebAuthn. These updates are backward compatible and require no changes to existing projects.

  • #​10390 0de88f5 Thanks @​gustavovalverde! - SCIM connections can now provision Users, Groups, and direct memberships into application-defined provisioning domains without the organization or SSO plugins. Applications can map Group membership to validated custom roles through projections. The service also supports SCIM 2.0 discovery, filtering, pagination, response attribute selection, atomic PATCH operations, and common request patterns used by Microsoft Entra ID and Okta.

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

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

  • #​10505 d701f90 Thanks @​gustavovalverde! - One Tap, Electron, and Expo client plugins now compose with createAuthClient without TypeScript errors, and the resulting client preserves each plugin's inferred actions.

  • #​10621 59c4c83 Thanks @​gustavovalverde! - Allow test instances to enable native database transactions for postgres and mysql.

  • Updated dependencies [5c45abc, 763a267, 5d38b13, 692b22c, ea06c5a, 3d04fab, 430c895, de8394d, dbd302e, 8784c1c, ecd83da, e4818b5, 7fe0e2b, 0683a5f, e7eb45b, 7c7313c, aedcb97, 03e6c94, 4f53b61, 2196ea6, 91f235f, 34558bc, 1e5b808, 93d3871, 97903c9, ed61b47, 0de88f5, 3a79aff, 41cca60, d701f90]:

v1.6.30

Compare Source

Patch Changes

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate.

This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [better-auth](https://better-auth.com) ([source](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth)) | pnpm.catalog.default | minor | [`1.6.29` → `1.7.0`](https://renovatebot.com/diffs/npm/better-auth/1.6.29/1.7.0) | --- ### Release Notes <details> <summary>better-auth/better-auth (better-auth)</summary> ### [`v1.7.0`](https://github.com/better-auth/better-auth/blob/HEAD/packages/better-auth/CHANGELOG.md#170) ##### Minor Changes - [#&#8203;8733](https://github.com/better-auth/better-auth/pull/8733) [`4e8e4c7`](https://github.com/better-auth/better-auth/commit/4e8e4c7fc5fb2723144cbf41c4a1bfa28de8d671) Thanks [@&#8203;bytaesu](https://github.com/bytaesu)! - Add `hydrateSession` to seed the client with a server-fetched session so `useSession` returns data on the first render. - [#&#8203;9930](https://github.com/better-auth/better-auth/pull/9930) [`0cbaf81`](https://github.com/better-auth/better-auth/commit/0cbaf81bed9dec4c56880ee78a532262386e1ec5) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Anonymous account linking now works after social and generic OAuth sign-in in Expo and other in-app browsers, where the OAuth callback returns without the session cookie. `onLinkAccount` fires and the anonymous user is migrated; before, it was silently skipped. Plugins can now carry server-trusted data across an OAuth redirect with the new `addOAuthServerContext` API, read back on the callback via `getOAuthState().serverContext`. Unlike `additionalData`, it cannot be set from the request body, so it is the right place for values the server must trust. For `@better-auth/oauth-provider`, the post-login authorization query now travels through that server-only channel, so it can no longer be injected through `additionalData`. - [#&#8203;10004](https://github.com/better-auth/better-auth/pull/10004) [`b36c38f`](https://github.com/better-auth/better-auth/commit/b36c38f9842d3416689340552989449a32007819) Thanks [@&#8203;bytaesu](https://github.com/bytaesu)! - The captcha plugin now requires endpoint entries to match full auth paths unless they use wildcard patterns. This prevents requests like `/sign-in//email` from bypassing captcha while preserving trailing-slash matches like `/sign-in/email/`. To protect multiple routes, replace partial paths like `/sign-in` with explicit wildcards such as `/sign-in/*` or `/sign-in/**`. - [#&#8203;10746](https://github.com/better-auth/better-auth/pull/10746) [`6782647`](https://github.com/better-auth/better-auth/commit/6782647d7c2d248246f9ef3980e656725c29ce64) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - OAuth device grants now use `oauthDeviceAuthorization()` alongside `oauthProvider()` or `mcp()`. This single integration replaces both the standalone `deviceCodeGrant()` plugin and the shared-grant configuration. Standalone Device Authorization no longer accepts or stores RFC 8707 resources, and `onDeviceAuthRequest` receives only `clientId` and `scope`. The OAuth integration rejects resource indicators that are not absolute, fragment-free URIs. The OAuth integration replaces the optional `resource` column with `oauthClientId` and `resources`. Regenerate and apply the schema when using it. Before upgrading from an earlier 1.7 prerelease, let pending OAuth device codes expire or delete them because they cannot be exchanged through the new integration. - [#&#8203;10402](https://github.com/better-auth/better-auth/pull/10402) [`763a267`](https://github.com/better-auth/better-auth/commit/763a2671c5372d88c291881977c8a1c2e29034b1) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Plugin database schemas can now define named or generated table-level indexes across multiple fields. SQL migrations and generated Drizzle or Prisma schemas resolve configured table and column names consistently, while the MongoDB adapter creates the same indexes before the first index-enforcing write. - [#&#8203;9766](https://github.com/better-auth/better-auth/pull/9766) [`bf39cbf`](https://github.com/better-auth/better-auth/commit/bf39cbf13f3b934f728cde72b1e7ebdc4c85f641) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Add a server-only `auth.api.consumePhoneNumberOTP` API for custom phone OTP flows that need to verify and consume a code without creating or updating users or sessions. - [#&#8203;10330](https://github.com/better-auth/better-auth/pull/10330) [`081d3c3`](https://github.com/better-auth/better-auth/commit/081d3c379c720926295067d878c421b5e8684c78) Thanks [@&#8203;ping-maxwell](https://github.com/ping-maxwell)! - Allow the username plugin's separate `displayUsername` field to be omitted by setting `displayUsername: false` on both the server and client plugins. - [#&#8203;10059](https://github.com/better-auth/better-auth/pull/10059) [`49b5cf6`](https://github.com/better-auth/better-auth/commit/49b5cf650e1264ecc4c917ca193ea05c3b58a3b9) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Device Authorization now creates unique database indexes for `deviceCode` and `userCode`, so each generated code must be unique in its column. Existing installations on every adapter must resolve duplicate values before applying the migration. MySQL and SQL Server installations must also convert both columns to bounded strings and clean up values longer than 191 characters before running it. Generated codes are limited to 191 characters. Issuance makes up to 3 attempts to overcome unique-key collisions, then returns `server_error` if it cannot create a unique `deviceCode` and `userCode`. Default-generated user codes accept case changes and readability separators during verification, approval, and denial; custom codes outside the default alphabet are matched exactly. The `/device` limiter allows 5 requests over a window equal to the configured code lifetime, while `/device/token` polling keeps its separate interval behavior. - [#&#8203;9645](https://github.com/better-auth/better-auth/pull/9645) [`e014029`](https://github.com/better-auth/better-auth/commit/e0140297a59ddb59cccbcb4ba46c513de8cb86a7) Thanks [@&#8203;ping-maxwell](https://github.com/ping-maxwell)! - Harden the Electron OAuth flow and tighten custom-scheme trusted-origin matching. The Electron sign-in flow now mandates PKCE S256. Plain PKCE is rejected: the `code_challenge_method` parameter is gone and every authorization code is verified by hashing the verifier with SHA-256. The server no longer trusts an `electron-origin` header to set the request Origin. The Electron client now sends a real `Origin` (for example `myapp:/`), so upgrade the `@better-auth/electron` client and server together and make sure your app's scheme is in `trustedOrigins`. The unused `disableOriginOverride` option is removed. Custom-scheme entries in `trustedOrigins` now match by scheme and authority instead of string prefix. A host-less entry such as `myapp://` or `exp://` still trusts every host of that scheme, but a host-bearing entry such as `myapp://callback` matches that host exactly, so it is no longer satisfied by `myapp://callback.attacker.tld`. - [#&#8203;9948](https://github.com/better-auth/better-auth/pull/9948) [`3d04fab`](https://github.com/better-auth/better-auth/commit/3d04fababbf3efd4c46a4012f46ed9397715c2e3) Thanks [@&#8203;yordis](https://github.com/yordis)! - feat(generic-oauth): add `refreshTokenParams` config to forward extra params on token refresh Multi-tenant OIDC providers (Zitadel multi-org, Auth0 with `audience`) need to send extra body params on the refresh call to rescope tokens without a full authorization redirect. The generic-oauth plugin now accepts a `refreshTokenParams` option (object or sync/async function) that is merged into the refresh request body, with `grant_type` and `refresh_token` protected from override. The function form receives request metadata for the request that triggered the refresh, so request-scoped data (headers, cookies) is available without out-of-band state like AsyncLocalStorage. `UpstreamProvider.refreshAccessToken` now accepts an optional second `ctx` argument; the change is backwards compatible because existing implementations that take only `refreshToken` remain valid. See [#&#8203;7554](https://github.com/better-auth/better-auth/issues/7554). - [#&#8203;9069](https://github.com/better-auth/better-auth/pull/9069) [`c7d2253`](https://github.com/better-auth/better-auth/commit/c7d22539ec4f7322d9625ae2953d397c3863d097) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Rewrite the generic OAuth plugin as a first-class social provider with OAuth 2.1 security defaults. Providers now use `signIn.social` + `callback/:id` instead of dedicated plugin endpoints, with PKCE required by default (OAuth 2.1), RFC 9207 issuer validation, OIDC auto-discovery with `openid` scope injection, and typed provider IDs. **Breaking changes:** - `signIn.oauth2({ providerId })` replaced by `signIn.social({ provider })` - `oauth2.link()` replaced by `linkSocial()` - Callback URL changed from `/api/auth/oauth2/callback/:id` to `/api/auth/callback/:id` - `genericOAuthClient()` removed; generic OAuth providers now use the standard social client APIs - `pkce` defaults to `true` (was `false`); set `pkce: false` for providers that reject PKCE - `authorizationUrlParams` and `tokenUrlParams` only accept `Record<string, string>` - `issuer` and `requireIssuerValidation` config fields removed; issuer validation is automatic via OIDC discovery - `mapProfileToUser` profile typed as `OAuth2UserInfo & Record<string, unknown>` - [#&#8203;9966](https://github.com/better-auth/better-auth/pull/9966) [`ec8a38c`](https://github.com/better-auth/better-auth/commit/ec8a38c08f5cfe2d922be0f8a49f2d0fa84de799) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - genericOAuth providers configured with a `discoveryUrl` now verify the provider's `id_token` against its published JWKS (signature, issuer, audience, and advertised algorithms) and bind it to the authorization request with a server-generated OIDC `nonce`. A sign-in whose `id_token` fails verification, or does not echo the expected `nonce`, is rejected. Set `disableIdTokenNonceBinding: true` on a provider that does not return the `nonce` claim in the authorization-code flow. These providers also accept client-submitted id\_token sign-in through `signIn.social({ idToken })`, which previously returned `ID_TOKEN_NOT_SUPPORTED`. Providers configured with explicit endpoints instead of `discoveryUrl` are unchanged. - [#&#8203;9368](https://github.com/better-auth/better-auth/pull/9368) [`430c895`](https://github.com/better-auth/better-auth/commit/430c89549060ef6bd477ed2510650b9e49bba560) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Generic OAuth users can now sign out from the configured OpenID provider when they call `authClient.signOut()`. When a provider exposes a discovered or configured logout endpoint, Better Auth redirects to it and includes the stored `id_token_hint` when available. Pass `callbackURL` or configure `postLogoutRedirectURI` for the return flow, with optional `state`, or set `disableRedirect` to handle the returned `url` yourself. When multiple linked providers support logout, Better Auth selects the most recently updated account. Set `disableProviderLogout: true` to keep sign-out local. - [#&#8203;9431](https://github.com/better-auth/better-auth/pull/9431) [`523f95c`](https://github.com/better-auth/better-auth/commit/523f95c10db24b790bbd75fe85c86c34d3465267) Thanks [@&#8203;pi0](https://github.com/pi0)! - feat: make `Auth` instance fetchable - [#&#8203;10577](https://github.com/better-auth/better-auth/pull/10577) [`5c45abc`](https://github.com/better-auth/better-auth/commit/5c45abcd2094d4a430cc84af6f9719fa0515ad71) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - 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` `WWW-Authenticate` challenge that names every missing scope. Clients can union those scopes into one authorization request instead of opening one browser redirect per scope. - Configure protected scopes with `requiredScopes` through `RequireMcpAuthOptions` or the matching `createMcpProtectedRequestHandler` verifier option. Exact membership remains the default; `isScopeSatisfied` can define hierarchical policies. - Use `createInsufficientScopeError` when an operation determines its required scopes dynamically. `createResourceServerChallenge` converts that signal and recognized token failures into safe RFC 6750 challenges. - Use `challengeScopes` only as the unauthenticated challenge hint. Handler-produced responses, ordinary permission denials, configuration failures, and unrelated thrown values keep their original status and identity. - [#&#8203;10403](https://github.com/better-auth/better-auth/pull/10403) [`dbd302e`](https://github.com/better-auth/better-auth/commit/dbd302e422c66620cde391f6a80ab90ee34182f9) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Scope account identity by trusted issuer instead of provider configuration. Accounts now use the unique `(issuer, accountId)` key, so aliases for one OpenID Connect issuer deduplicate one external identity while equal subjects from different issuers remain separate. This identity deduplication does not introduce independent grant or provider lifecycle records for aliases. This release requires `Account.issuer` but preserves `Account.accountId` as the provider-assigned account identifier. Account-specific APIs select the local `Account.id` through the `accountId` request property; token and provider-profile APIs can instead select the signed account cookie with `useAccountCookie: true`. Credential accounts use `local:credential` and the linked user's stable `id` as their provider identity. OAuth provider identity now comes from raw verified profiles. OpenID Connect discovery uses `sub`, plain OAuth uses `id`, and providers can declare `accountSubject` for another immutable field; Better Auth no longer switches between `sub` and `id` at runtime. `getUserInfo().user` no longer carries provider identity, and `mapProfileToUser` cannot return `id`. Read the selected identity from `accountInfo.account.accountId` instead of `accountInfo.user.id`. The generic `microsoftEntraId` helper now requires a concrete tenant GUID; use the built-in Microsoft provider for multi-tenant authorities. SSO account subjects are now protocol-defined. OIDC uses the verified `sub` claim, and SAML uses the signed `NameID`; `mapping.id` is removed from both configurations. A manual SAML configuration without metadata XML must set `idpMetadata.entityID`, because `samlConfig.issuer` identifies the service provider and no longer acts as the IdP identity. Apply the reviewed account-identity backfill in the Better Auth 1.7 upgrade guide before deploying. The generated schema migration cannot assign trusted issuers or resolve existing identity collisions automatically. - [#&#8203;10359](https://github.com/better-auth/better-auth/pull/10359) [`8784c1c`](https://github.com/better-auth/better-auth/commit/8784c1c1f4301acf96d980e5bf81ff56435e2545) Thanks [@&#8203;ping-maxwell](https://github.com/ping-maxwell)! - Database joins have moved out of `experimental` into a stable option at `advanced.database.joins` (default: `false`). If you previously set `experimental: { joins: true }`, update your config to: ```ts advanced: { database: { joins: true, }, } ``` Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (`npx auth@latest generate`). - [#&#8203;9992](https://github.com/better-auth/better-auth/pull/9992) [`e53582c`](https://github.com/better-auth/better-auth/commit/e53582ce55a0ddbca62f52efeb3459523816f222) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - The MCP plugin moves out of `better-auth` into its own package, `@better-auth/mcp`, built on `@better-auth/oauth-provider`. Import the authorization plugin and protected-request helpers from the package root. The in-core MCP client (`createMcpAuthClient` and its adapters) is removed; MCP protocol and transport clients come from the official version 2 `@modelcontextprotocol/client` and `@modelcontextprotocol/server` packages. The OAuth endpoints move from `/mcp/*` to `/oauth2/*`, with discovery at `/.well-known/oauth-authorization-server` and protected resource metadata at `/.well-known/oauth-protected-resource`. Discovery-based MCP clients pick up the new locations on their own. The shared-auth route helper is renamed from `withMcpAuth` to `requireMcpAuth`. The standalone protected-resource factory is renamed from `mcpHandler` to `createMcpProtectedRequestHandler`; pass one flat `McpProtectedRequestHandlerOptions` object with `issuer`, a single `audience`, optional `jwtVerifyOptions`, token-verification fields, and challenge fields. Its callback receives `accessTokenClaims`. `requireMcpAuth` verifies the access token against the published JWKS, validates DPoP proofs for DPoP-bound tokens, and passes the verified access-token claims to your handler. `createInsufficientScopeError` now validates a custom description against the RFC 6750 `error_description` character set when the error is constructed. Invalid descriptions throw `TypeError("invalid error_description")` before an error can reach resource-challenge serialization. MCP 2026-07-28 uses a stateless request and response transport. Serve MCP routes with version 2 of `@modelcontextprotocol/server`, configure `createMcpHandler` with `legacy: "reject"`, wrap it with `requireMcpAuth`, and export only `POST`. Remove MCP-route `GET` and `DELETE` exports and session-store options such as `redisUrl`. OAuth clients, consent, authorization codes, refresh tokens, and security records remain durable authorization state. To migrate, install `@better-auth/mcp`, `@better-auth/cimd`, and the official version 2 MCP client or server package needed by your application; add the `jwt()` plugin, which is now required for token signing; and move options that were nested under `oidcConfig` to flat options on `mcp({ ... })`. The database models change: `oauthApplication` becomes `oauthClient`, with new `oauthRefreshToken` and `oauthClientAssertion` tables. Regenerate or migrate your schema with `npx auth migrate` or `npx auth generate`. - [#&#8203;10204](https://github.com/better-auth/better-auth/pull/10204) [`0683a5f`](https://github.com/better-auth/better-auth/commit/0683a5f36befb45ade3866c7f8057791eadeee59) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Microsoft sign-in now identifies Entra accounts with the stable `oid` claim in both the built-in `microsoft` provider and the Generic OAuth `microsoftEntraId` helper. Tokens without a valid `oid` are rejected, and the Generic OAuth helper refuses to initialize unless Microsoft discovery provides ID-token verification metadata. Existing Microsoft account rows created from `sub` must be migrated before upgrading. - [#&#8203;9305](https://github.com/better-auth/better-auth/pull/9305) [`e7eb45b`](https://github.com/better-auth/better-auth/commit/e7eb45b065903f5fccddae491696cb069814a3c8) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - feat(oauth): per-request `additionalParams` and `loginHint` parity across `signIn.social`, `linkSocial`, and `signIn.sso` Unified escape hatch for customizing the provider authorization URL on a per-request basis. Previously, dynamic parameters like Google's `access_type=offline` / `prompt=consent`, Cognito's `identity_provider=Google`, or Microsoft's `domain_hint` could only be set as static server configuration. ##### New capabilities - `signIn.social`, `linkSocial`, and `signIn.sso` accept `additionalParams: Record<string, string>`. Values are appended to the authorization URL as query parameters. - `linkSocial` also accepts `loginHint`, matching the surface of `signIn.social` and `signIn.sso`. - `OAuthProvider.createAuthorizationURL` gains `additionalParams` in its input contract; every built-in provider forwards it to the shared helper. - Generic-OAuth providers merge call-time `additionalParams` with the config-level `authorizationUrlParams`; call-time wins on key collision. - Cognito exposes a typed `identityProvider?: string` config option that maps to the `identity_provider` query parameter, avoiding magic strings. ##### Security - The shared `createAuthorizationURL` helper silently drops any caller-supplied key in `RESERVED_AUTHORIZATION_PARAMS` (`state`, `client_id`, `redirect_uri`, `response_type`, `code_challenge`, `code_challenge_method`, `nonce`, `scope`). The request-body Zod schema rejects the same keys with 400, so misuse is visible at the edge rather than silently overriding security-critical parameters. `nonce` is reserved so a caller cannot replace the OIDC nonce Better Auth generates when binding a discovery provider's `id_token` to the authorization request. - Providers that use non-standard client identifiers (`wechat` → `appid`, `tiktok` → `client_key`) additionally filter those keys so a caller cannot swap the configured OAuth app. - Provider protocol constants that are required for the integration to function (`atlassian` → `audience`, `notion` → `owner`) are merged last so caller-supplied `additionalParams` cannot override them. Configured defaults that represent operator intent (e.g. Google `include_granted_scopes`, Cognito `identityProvider`) remain caller-overridable. - `signIn.sso` rejects `additionalParams` with 400 when the resolved provider is SAML; the SAML AuthnRequest is signed and cannot carry caller-supplied query parameters, so silently dropping them would mislead integrators. ##### OpenAPI - Added `ZodRecord` handling to the OpenAPI generator so `z.record()` fields emit `type: object` with typed `additionalProperties`. Incidentally fixes a long-standing bug where `additionalData` was rendered as `type: string`. ##### Refactors - `discord`, `roblox`, `zoom`, and `slack` providers now delegate to the shared `createAuthorizationURL` helper and inherit its RFC behavior and reserved-key guard. - `tiktok` and `wechat` keep their manual URL construction (non-standard OAuth2 parameter names and URL fragment requirements) but thread `additionalParams` with the same reserved-key filter. Closes [#&#8203;2351](https://github.com/better-auth/better-auth/issues/2351). Closes [#&#8203;5441](https://github.com/better-auth/better-auth/issues/5441). Closes [#&#8203;5592](https://github.com/better-auth/better-auth/issues/5592). Closes [#&#8203;5604](https://github.com/better-auth/better-auth/issues/5604). Supersedes [#&#8203;4992](https://github.com/better-auth/better-auth/issues/4992) and [#&#8203;5443](https://github.com/better-auth/better-auth/issues/5443). - [#&#8203;10127](https://github.com/better-auth/better-auth/pull/10127) [`7c7313c`](https://github.com/better-auth/better-auth/commit/7c7313c8189baabd11a2ecb681bd2b16eb40fa4d) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - OAuth sign-in, account linking, callback, and proxy flows now build `redirect_uri` from the current request base URL when `baseURL.allowedHosts` is configured. Built-in social providers and generic OAuth providers now use the resolved request host for redirects in multi-host deployments. Custom `OAuthProvider` implementations can omit `callbackPath` when using the shared `/callback/<provider-id>` route. Set `callbackPath` only for custom callback routes. - [#&#8203;10039](https://github.com/better-auth/better-auth/pull/10039) [`aedcb97`](https://github.com/better-auth/better-auth/commit/aedcb974f055c3514fe0464dc53d71d45a8a1725) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - feat(oauth-provider)!: DPoP-bound access tokens (RFC 9449) OAuth provider integrations can issue and verify DPoP sender-constrained tokens. Clients request them with `dpop_bound_access_tokens` at registration, `dpop_jkt` on the authorization request, or by targeting a resource configured with `dpopBoundAccessTokensRequired`. Issued tokens carry `cnf.jkt`, return `token_type: "DPoP"`, and stay bound through refresh-token rotation, introspection, and userinfo. Resource servers verify DPoP requests with `verifyAccessTokenRequest`, which checks the `Authorization: DPoP` scheme, the proof, the request target, the access-token hash, and proof replay. The MCP package advertises DPoP in protected resource metadata and verifies DPoP-bound requests. Proof replay is rejected through the database-backed verification store, so anti-replay holds across instances. `verifyAccessTokenRequest` and `requireMcpAuth` use that store by default; build one with `createDpopReplayStore(internalAdapter)` or pass a custom `dpop.replayStore`. This needs database-backed verification storage: a secondary-storage-only deployment rejects DPoP requests rather than skipping replay protection. Breaking: the raw-token verifier `verifyAccessToken` is renamed to `verifyBearerToken`, both in `better-auth/oauth2` and as the `oauthProviderResourceClient` action, and it rejects DPoP-bound tokens. Use `verifyAccessTokenRequest` on any endpoint that may receive them. The resource-request input type is renamed from `AccessTokenRequestInput` to `ResourceRequestInput`, and the DPoP algorithm option is `signingAlgorithms` everywhere. Run a schema migration for the DPoP token-binding fields: the `confirmation` column on the access-token and refresh-token tables. DPoP-bound clients also gain `dpopBoundAccessTokens` and resources `dpopBoundAccessTokensRequired`. No dedicated replay table is added; proof replay reuses the verification store. - [#&#8203;9828](https://github.com/better-auth/better-auth/pull/9828) [`4f53b61`](https://github.com/better-auth/better-auth/commit/4f53b61f49b470a40ccab18fe1fe4d80f225905f) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Verify social-provider id\_tokens with a single shared verifier. Client-submitted id\_token sign-in (`signIn.social({ idToken })` and account linking) is verified by one function instead of a per-provider `verifyIdToken` method. Each provider declares an `idToken` config with a JWKS source, issuer, and audience, and the core verifier runs the signature, issuer, audience, and nonce checks. A provider that declares no config rejects the client id\_token path. PayPal previously accepted any decodable id\_token without verifying its signature. PayPal derives identity from the access token, so it now declares no `idToken` config, and the client id\_token path returns `ID_TOKEN_NOT_SUPPORTED`. PayPal sign-in through the redirect flow is unchanged. Custom providers that implement `UpstreamProvider` directly replace the removed `verifyIdToken` method with an `idToken` config: ```ts idToken: { jwks: createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")), issuer: "https://issuer.example", audience: clientId, }, ``` For verification that cannot use a local JWKS, pass `idToken: { verify: async (token, nonce) => boolean }`. The `verifyIdToken` and `disableIdTokenSignIn` provider options are unchanged. - [#&#8203;9079](https://github.com/better-auth/better-auth/pull/9079) [`6f2948e`](https://github.com/better-auth/better-auth/commit/6f2948e87bb5fa14bd2174a91f7143e1eced1b87) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - feat(oauth-provider): compute `at_hash` in ID tokens per OIDC Core §3.1.3.6 ID tokens issued alongside an access token now include the `at_hash` claim, which cryptographically binds the two tokens to prevent token substitution attacks. The hash algorithm is selected based on the actual signing key's algorithm (EdDSA/Ed25519 uses SHA-512, RS/ES/PS384 uses SHA-384, RS/ES/PS512 uses SHA-512, all others use SHA-256). A new `resolveSigningKey()` export is available from `better-auth/plugins` to resolve the current JWKS signing key (including its algorithm). When using a custom `jwt.sign` callback, the signed ID token's header is validated against the declared algorithm to prevent `at_hash` mismatches. - [#&#8203;10135](https://github.com/better-auth/better-auth/pull/10135) [`f68044d`](https://github.com/better-auth/better-auth/commit/f68044dcfbd9fb83763249ed9509cfacbcce47be) Thanks [@&#8203;brentmitchell25](https://github.com/brentmitchell25)! - Registered OAuth clients can now use the RFC 8628 device flow to obtain OAuth access tokens. Add `oauthDeviceAuthorization()` alongside `oauthProvider()` or `mcp()`, request a code at `/device/code`, and exchange it at `/oauth2/token` after the user approves it. OAuth and OpenID discovery advertise the `device_authorization_endpoint`. Device authorization requests can bind RFC 8707 resource indicators. `GET /device` returns the requested client, scopes, and resources to the authenticated user who owns the request. Token requests can reuse or narrow the approved resources, but cannot add new ones. Existing first-party device clients continue to receive Better Auth session tokens from `/device/token`. Enabling `oauthDeviceAuthorization()` adds nullable `oauthClientId` and `resources` fields to `deviceCode`. Regenerate and apply the database schema after adding the integration. Confidential clients authenticate at `/device/code` with their registered method, while public clients send `client_id`. Empty `client_id`, `scope`, `user_id`, and authentication values are treated as omitted; multiple non-empty values for any of these parameters return `invalid_request`, while multiple `resource` values remain supported. Unknown OAuth client IDs enter the standalone device flow only when `oauthDeviceAuthorization({ validateClient })` accepts them. - [#&#8203;9929](https://github.com/better-auth/better-auth/pull/9929) [`91f235f`](https://github.com/better-auth/better-auth/commit/91f235f8604cd432749adf18c7bd7d658aa1519b) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Add `requireEmailVerification` to OAuth provider options, for built-in social providers and the Generic OAuth plugin. When a provider reports an unverified email, the user and account are still created or linked, but no session is issued: the OAuth callback redirects with `?error=email_not_verified`, and ID token and One Tap sign-in return `403` `EMAIL_NOT_VERIFIED`. Verification emails follow the existing `emailVerification.sendOnSignUp` / `sendOnSignIn` settings. It is opt-in per provider and does not inherit `emailAndPassword.requireEmailVerification`, so existing social logins keep working. The gate checks the local user's verification state, so a user verified through another method keeps access. Only enable it for providers that report a trustworthy `email_verified` signal. - [#&#8203;9648](https://github.com/better-auth/better-auth/pull/9648) [`d2a79ba`](https://github.com/better-auth/better-auth/commit/d2a79bae79b88e2b28cb678f5eefd9759239b627) Thanks [@&#8203;brentmitchell25](https://github.com/brentmitchell25)! - OAuth provider now models protected resources explicitly. Configure them with `resources` or create them through the `oauthResource` admin API. Each resource can define token TTLs, allowed scopes, custom JWT claims, and JWT signing pins. `validAudiences` is removed. Move each existing resource identifier into `resources`; link clients that should be limited to specific resources through `oauthClientResource` or Dynamic Client Registration `resources`. Access-token issuance now applies resource policy to the requested RFC 8707 `resource` values. The OAuth provider narrows scopes to resource allowlists, uses the shortest configured TTL, strips reserved RFC 9068 claim names from custom claims, emits `jti`, and keeps repeated `resource` form parameters. Refresh-token TTLs now use the shortest applicable lifetime. Deployments with a per-resource `refreshTokenTtl` longer than `refreshTokenExpiresIn` will see refresh tokens expire at the provider default instead of the longer resource value. JWT signing can now honor per-resource pins. `signJWT()` accepts `signingKeyId` and `signingAlgorithm`; JWKS adapters expose `getKeyById()` and `getLatestKeyByAlg()`. The `jwks` table adds nullable `alg` and `crv` columns, and `keyPairConfigs` can provision multiple algorithms in one keyring. After upgrading, run `npx auth generate` and apply the migration before deploying. The migration adds `oauthResource`, `oauthClientResource`, and the new `jwks` columns. Without it, resources using `signingAlgorithm` cannot find matching keys. Resource servers should publish RFC 9728 protected-resource metadata at their own origin. The OAuth provider exposes challenge helpers that point clients at that metadata. `@better-auth/mcp` now requires an explicit `resource` option. The plugin stores that identifier as an OAuth resource, publishes RFC 9728 protected-resource metadata for it, and binds issued access tokens to that resource. Existing `mcp({ loginPage, consentPage })` setups should add a protected MCP resource identifier, for example `resource: "https://api.example.com/mcp"`. - [#&#8203;10397](https://github.com/better-auth/better-auth/pull/10397) [`bb6c102`](https://github.com/better-auth/better-auth/commit/bb6c1021e8f6200e60ff852cbd95fb6841a0ec4b) Thanks [@&#8203;ping-maxwell](https://github.com/ping-maxwell)! - Add `organization.getOrganization()` to fetch organization metadata without members or invitations. - [#&#8203;8931](https://github.com/better-auth/better-auth/pull/8931) [`34558bc`](https://github.com/better-auth/better-auth/commit/34558bc52b0e043021a1072f78de5f5439ae1734) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Add opt-in JWKS-backed asymmetric JWT support for `session_data` cookie cache tokens, so services can verify cookie-cache JWTs with public keys instead of shared secrets. Enable it with `jwt({ sessionCookieCache: true })` alongside `session.cookieCache.strategy = "jwt"`. - [#&#8203;8977](https://github.com/better-auth/better-auth/pull/8977) [`954b664`](https://github.com/better-auth/better-auth/commit/954b664f4f251f8dd028451dab3ab43067dbf890) Thanks [@&#8203;ruban-s](https://github.com/ruban-s)! - allow passing `userId` and `organizationId` to the `listUserTeams` API. `userId` lets callers list teams for another member of an organization (gated behind the `member:update` permission). `organizationId` scopes the result to a specific organization without needing to switch the session's active organization, matching the pattern used by `addTeamMember`/`removeTeamMember`. - [#&#8203;9969](https://github.com/better-auth/better-auth/pull/9969) [`76a3342`](https://github.com/better-auth/better-auth/commit/76a33429fc2a3edcc85307bf81b9d92a95f9de6c) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Signing out with `secondaryStorage` and `session.preserveSessionInDatabase` now runs your configured `session.delete` hooks and marks the preserved session row as ended. OAuth Provider access and refresh tokens bound to that session are revoked and back-channel logout is dispatched on sign-out. Previously these hooks were skipped in this setup, so the tokens stayed valid until they expired. - [#&#8203;9657](https://github.com/better-auth/better-auth/pull/9657) [`1e5b808`](https://github.com/better-auth/better-auth/commit/1e5b80847208cf839c9d45363ca19b8eab41c68a) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Harden `private_key_jwt` and token endpoint client authentication, and add the helpers that make the fix structural. `@better-auth/core/oauth2` now exposes `encodeBasicCredentials` and `decodeBasicCredentials`, a round-trip-tested pair that follows RFC 6749 §2.3.1 (`application/x-www-form-urlencoded` each value, split on the first `:` only). The decoder accepts the scheme case-insensitively and tolerates one or more spaces before the credentials per RFC 7235 §2.1. `client_secret_basic` on the client side and the Better Auth OAuth provider on the server side both go through these helpers, so credentials containing reserved characters round-trip cleanly across the stack and headers like `basic xxx` or `Basic xxx` are accepted. `createPrivateKeyJwtClientAssertionGetter` validates options eagerly. Unsupported algorithms (`HS256`, `none`), a JWK with no key material, and disagreement between an explicit `algorithm` and the JWK-embedded `alg` all throw at construction rather than on the first token request. `signPrivateKeyJwtClientAssertion` enforces the same checks for direct callers. **Breaking:** configurations that paired an unsupported JWK `alg` with a different explicit `algorithm` used to silently sign with the explicit option; they now fail at construction. **Breaking:** `@better-auth/oauth-provider` accepts client `jwks` metadata only as an RFC 7517 JWK Set object with a non-empty `keys` array. Replace `jwks: [key]` with `jwks: { keys: [key] }` in DCR payloads, administrative and user client creation, Client ID Metadata Documents, test fixtures, and generated client code. Remotely fetched `jwks_uri` responses must use the same object shape. EC keys must use P-256, P-384, or P-521; OKP keys must use [`Ed25519`](https://github.com/better-auth/better-auth/commit/Ed25519). When a key declares `alg`, it must be a supported `private_key_jwt` algorithm that matches the key type and curve; omit `alg` when the client chooses the algorithm in its assertion header. OAuth client rows previously written through `oauthToSchema` are already stored as JWK Set objects, so this is a request, configuration, and type migration rather than another database rewrite; audit rows written outside Better Auth separately. The SSO `private_key_jwt` flow redirects with `error_description=no_private_key_available` when a `resolvePrivateKey` callback returns no `privateKeyJwk` or `privateKeyPem`. The redirect path previously short-circuited only when the resolver was absent entirely; an empty resolver return fell through into an internal signing error. `better-auth/test` adds `getHttpTestInstance`, a counterpart to `getTestInstance` that binds a real HTTP listener on an OS-assigned port and constructs the auth instance against the discovered URL. It removes the temp-server-then-rebind race that test files have been individually copy-pasting. - [#&#8203;8836](https://github.com/better-auth/better-auth/pull/8836) [`93d3871`](https://github.com/better-auth/better-auth/commit/93d3871bd2f7c2fdd423c4c88a22a50b6333e656) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Add client authentication configuration for token endpoint requests across the stack, including `private_key_jwt` (RFC 7523). Generic OAuth providers now accept `tokenEndpointAuth` for token endpoint client authentication. Use `tokenEndpointAuth: { method: "private_key_jwt", getClientAssertion }` for JWT client assertions, `{ method: "none" }` for public clients, and `{ method: "client_secret_basic" }` or `{ method: "client_secret_post" }` with `clientSecret` for explicit secret-based client authentication. The existing `authentication: "basic" | "post"` option remains available for secret-based token requests. Use `createPrivateKeyJwtClientAssertionGetter()` to sign RFC 7523 assertions from a private key. The assertion getter receives `{ clientId, tokenEndpoint, grantType }`, so integrations do not duplicate client ID or token endpoint values inside assertion helpers. Core OAuth2 now exports private-key JWT-specific helpers and types: `signPrivateKeyJwtClientAssertion`, `createPrivateKeyJwtClientAssertionGetter`, `PrivateKeyJwtSigningAlgorithm`, and `PRIVATE_KEY_JWT_SIGNING_ALGORITHMS`. Token endpoint client authentication parameters are derived from `clientId`, `clientSecret`, and `tokenEndpointAuth`. Configured token endpoint authentication requires `clientId`; secret-based token endpoint authentication also requires `clientSecret`. Custom token parameters are for provider-specific fields and do not replace the configured client authentication values. `refreshAccessToken()` now forwards `resource` values to refresh-token requests, so RFC 8707 resource indicators work through both the high-level refresh helper and `refreshAccessTokenRequest()`. The synchronous OAuth2 request builders `createAuthorizationCodeRequest`, `createRefreshAccessTokenRequest`, and `createClientCredentialsTokenRequest` have been removed. Use the async `authorizationCodeRequest`, `refreshAccessTokenRequest`, and `clientCredentialsTokenRequest` helpers instead. Servers verify JWT client assertions signed with asymmetric keys, and clients can use the same token endpoint authentication contract for authorization code, refresh, and client credentials token requests. - [#&#8203;9134](https://github.com/better-auth/better-auth/pull/9134) [`652fa53`](https://github.com/better-auth/better-auth/commit/652fa53e4912837fe234651e7c7705fb35abe188) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - The dynamic `baseURL` config now ignores `x-forwarded-host` and `x-forwarded-proto` unless you set `advanced.trustedProxyHeaders: true`. Requests using `baseURL: { allowedHosts }` now resolve the auth origin from `Host` by default, so forwarded headers cannot select another allowed host unless trusted proxy headers are enabled. **Breaking change:** if your proxy exposes the public hostname only through `x-forwarded-host`, set `advanced.trustedProxyHeaders: true`. Deployments where the proxy rewrites `Host` to the public hostname (nginx default, Vercel, Cloudflare, and Netlify) are unaffected. **Migration:** ```ts betterAuth({ baseURL: { allowedHosts: [...] }, advanced: { trustedProxyHeaders: true, }, }); ``` - [#&#8203;9240](https://github.com/better-auth/better-auth/pull/9240) [`729c00d`](https://github.com/better-auth/better-auth/commit/729c00d74c94f558893da1e3a9ee86451d1b23da) Thanks [@&#8203;adrianmxb](https://github.com/adrianmxb)! - feat(username): add immutable username option This allows users to set their username during sign-up or first update, but prevents changing it to a different value afterwards. Users can still update other profile fields. - [#&#8203;10031](https://github.com/better-auth/better-auth/pull/10031) [`6fe9faa`](https://github.com/better-auth/better-auth/commit/6fe9faab65eb640dbe9bb762954a068586e8661c) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Remove the deprecated `oidcProvider` plugin from `better-auth/plugins`. Migrate OIDC authorization-server integrations to `@better-auth/oauth-provider`. - [#&#8203;10473](https://github.com/better-auth/better-auth/pull/10473) [`ed61b47`](https://github.com/better-auth/better-auth/commit/ed61b4798e0ccedadc3b0c0e0a2d08b5d4b7ed5a) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Add transactional OIDC user resolution so applications can link verified issuer and subject pairs to exact existing users while preserving or updating the local profile. - [#&#8203;10234](https://github.com/better-auth/better-auth/pull/10234) [`973fdde`](https://github.com/better-auth/better-auth/commit/973fdde79d9746b15d5ac0427049e8a008a7705c) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - The SIWE plugin now issues nonces before the wallet address or Chain ID is known. `authClient.siwe.nonce()` and `authClient.siwe.getNonce()` no longer accept wallet fields, `getNonce` must return an ERC-4361 nonce (8-250 alphanumeric characters), and SIWE verification now reads the wallet address and Chain ID from the signed ERC-4361 message. - [#&#8203;9864](https://github.com/better-auth/better-auth/pull/9864) [`41cca60`](https://github.com/better-auth/better-auth/commit/41cca606d14e7b8a1d16da662d644ca39fe4281f) Thanks [@&#8203;GautamBytes](https://github.com/GautamBytes)! - Add a `user.validateUserInfo` provisioning gate that lets applications reject an identity before a user is created or a new account is linked. It runs once at the creation step for every method that provisions a user (OAuth, SSO/SAML, email/password, magic link, email OTP, anonymous, SIWE, phone number, admin-created users, and SCIM), including stateless setups with no persistent database. It also re-runs when an existing OAuth or SSO user signs in again (`source.action` is `"sign-in"`), where it receives the fresh provider email and profile so a domain or org policy can reject a user whose provider identity moved out of bounds. Non-provider returning sign-ins are not re-validated. The callback receives the mapped `user` plus a `source` describing the `action` (`create-user`, `link-account`, or `sign-in`), the `method`, and provider metadata: `source.oauth` for OAuth providers and `source.sso` for OIDC/SAML SSO providers. Return `{ error, errorDescription }` to reject: browser flows redirect to the error URL and programmatic flows return a `403`. - [#&#8203;10036](https://github.com/better-auth/better-auth/pull/10036) [`ad35ead`](https://github.com/better-auth/better-auth/commit/ad35eadd130162565a1b93c27f3a66910dca0b0e) Thanks [@&#8203;bytaesu](https://github.com/bytaesu)! - Require Google One Tap server callbacks to resolve a Google client ID before verifying ID tokens. Configure `oneTap({ clientId })` or `socialProviders.google.clientId` when using the One Tap plugin. - [#&#8203;9057](https://github.com/better-auth/better-auth/pull/9057) [`544f1c6`](https://github.com/better-auth/better-auth/commit/544f1c63c9826831d96a126fbe568d8a8a8fde68) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - feat(two-factor)!: add OTP-only enablement and a discriminated response `enableTwoFactor` now accepts a `method` parameter (`"otp" | "totp"`, default `"totp"`) and returns a discriminated response with a `method` field. ##### `method: "otp"` - Sets `twoFactorEnabled: true` immediately. - Returns `{ method: "otp" }`. - Requires `otpOptions.sendOTP` to be configured on the server; rejects with `OTP_NOT_CONFIGURED` otherwise. ##### `method: "totp"` (default) - Returns `{ method: "totp", totpURI, backupCodes }`. - Rejects with `TOTP_NOT_CONFIGURED` if `totpOptions.disable` is set. The existing `skipVerificationOnEnable` option remains supported for TOTP enrollment. ##### Breaking changes - **Response shape changed**: `enableTwoFactor` includes a `method` field in the response (`"otp"` or `"totp"`). ##### Patch Changes - [#&#8203;10014](https://github.com/better-auth/better-auth/pull/10014) [`73541c1`](https://github.com/better-auth/better-auth/commit/73541c119041113b1909fe244ff4b8210618b5b5) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Cloudflare Workers apps can now start when importing Better Auth subpaths such as `better-auth/db`. Beta builds were crashing during module initialization before application code ran. - [#&#8203;10299](https://github.com/better-auth/better-auth/pull/10299) [`cf8eaac`](https://github.com/better-auth/better-auth/commit/cf8eaac26e11bcdb7309d537f1730b2559963861) Thanks [@&#8203;momomuchu](https://github.com/momomuchu)! - widen drizzle-kit peer dependency range - [#&#8203;10501](https://github.com/better-auth/better-auth/pull/10501) [`65fc17c`](https://github.com/better-auth/better-auth/commit/65fc17c755c3e2c8c77d5b401d612737764c219d) Thanks [@&#8203;KingIronMan2011](https://github.com/KingIronMan2011)! - Expand the optional `drizzle-orm` peer range to `^0.45.2 || >=1.0.0-rc.1 <2.0.0`, matching `@better-auth/drizzle-adapter` and allowing Drizzle ORM v1 RC installations without peer dependency warnings. - [#&#8203;10622](https://github.com/better-auth/better-auth/pull/10622) [`ecd83da`](https://github.com/better-auth/better-auth/commit/ecd83daa01ec482d31667019737cb6697f03da0b) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Sign-up no longer deadlocks when session cookie caching uses the JWT strategy on a single-connection SQLite database with native transactions enabled. JWKS key lookups and creation now resolve the transaction-scoped adapter instead of always querying the root connection, so minting a signing key during sign-up joins the surrounding transaction instead of racing it for the only available connection. On multi-connection databases (Postgres, MySQL) this also fixes a silent atomicity gap where a JWKS key created mid-transaction could commit independently of the transaction it was minted in. - [#&#8203;10293](https://github.com/better-auth/better-auth/pull/10293) [`fe4c820`](https://github.com/better-auth/better-auth/commit/fe4c8209479cc90a4c2e8692b2d84fde26926af2) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - `npx auth migrate` can now add required columns with static defaults and nullable unique columns to existing SQLite, PostgreSQL, and MySQL tables. Required unique columns still need distinct values to be backfilled manually before applying the unique constraint. - [#&#8203;9898](https://github.com/better-auth/better-auth/pull/9898) [`7fe0e2b`](https://github.com/better-auth/better-auth/commit/7fe0e2b165c17207a43863b0f1c12c401976d6b2) Thanks [@&#8203;ItalyPaleAle](https://github.com/ItalyPaleAle)! - Add `clientAssertion` support to the Microsoft Entra ID social provider. - [#&#8203;9301](https://github.com/better-auth/better-auth/pull/9301) [`03e6c94`](https://github.com/better-auth/better-auth/commit/03e6c94e965a7e87c1d44074b8e90257cb1f1cd2) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Add `allowIdpInitiated` to `GenericOAuthConfig` and SSO `OIDCConfig` to support providers that initiate OAuth without a `state` parameter (e.g. Clever). When enabled, stateless callbacks restart the OAuth flow server-side with fresh state and PKCE, preserving CSRF protection. Also hardens `parseState` against undefined request bodies on GET callbacks. - [#&#8203;10065](https://github.com/better-auth/better-auth/pull/10065) [`2196ea6`](https://github.com/better-auth/better-auth/commit/2196ea65e724830d9f1066c6593210579de586b9) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - OAuth and device-authorization responses that carry credentials now consistently send `Cache-Control: no-store` and `Pragma: no-cache`, so proxies, CDNs, and browsers never cache them. This covers the token, introspection, and userinfo endpoints, dynamic and admin client registration, client secret rotation, and the device code and device token responses, including the error responses from those endpoints. Endpoints declare this with `metadata: { noStore: true }`, and the header set is exported from `@better-auth/core` as `NO_STORE_HEADERS` for responses built by hand. - [#&#8203;10124](https://github.com/better-auth/better-auth/pull/10124) [`06daf70`](https://github.com/better-auth/better-auth/commit/06daf7011e548ef5a7d513c96e3a440331977a7d) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Preserve the resolved OAuth user when `overrideUserInfo` returns `null` during account linking. - [#&#8203;9304](https://github.com/better-auth/better-auth/pull/9304) [`e0d2b9e`](https://github.com/better-auth/better-auth/commit/e0d2b9eb9b4a515e1b73be71e1e3681faaa9b55f) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Propagate sign-out to every connected app and cut off API access immediately, via OIDC Back-Channel Logout 1.0. When a user's session ends at the OP (sign-out, `/oauth2/end-session`, admin revoke, ban), `@better-auth/oauth-provider` now notifies every Relying Party that holds tokens for that session. The user's API access is cut off right away, instead of access tokens staying usable until their own TTL. Each client opts in by registering a `backchannel_logout_uri` (and optionally `backchannel_logout_session_required`) via DCR or the admin client-create endpoint. The provider signs a `logout+jwt` Logout Token per client and POSTs it to that client in parallel, with a short per-RP timeout. **Breaking change.** Introspection of an opaque or JWT access token whose bound session has ended now returns `{ active: false }`, and `/oauth2/userinfo` rejects it with `invalid_token`. Previously the token stayed active until its own TTL. If you relied on access tokens outliving the user's session, that no longer holds. Refresh tokens without `offline_access` are revoked on session end; `offline_access` refresh tokens are preserved so long-lived API access can survive the browser session (OIDC Back-Channel Logout 1.0 §2.7). Access-token invalidation on session end is an additional OP hardening choice beyond §2.7, enforced by session liveness, so it holds even when the JWT plugin is disabled. Delivery runs through the host's background task handler when one is configured (Vercel `waitUntil`, Cloudflare `ctx.waitUntil`); without a handler it completes inline so notifications are not lost on request teardown. Configure `advanced.backgroundTasks.handler` on serverless runtimes to keep sign-out fast. Discovery at `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server` advertises `backchannel_logout_supported: true` and `backchannel_logout_session_supported: true` when the JWT plugin is enabled. Every registered `backchannel_logout_uri` must be a credential-free public HTTPS URL without a fragment; loopback HTTP is rejected for both public and confidential clients. CIMD documents cannot register back-channel logout metadata. The SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, also covers a `private_key_jwt` client's `jwks_uri`. Schema changes on `@better-auth/oauth-provider`: - `oauthClient.backchannelLogoutUri: string | null` - `oauthClient.backchannelLogoutSessionRequired: boolean` - `oauthAccessToken.revoked: Date | null` `better-auth`'s `signJWT` gains an optional `header` argument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such as `typ: "logout+jwt"`, can now set it without reaching for the low-level signing primitives. - [#&#8203;10125](https://github.com/better-auth/better-auth/pull/10125) [`a83152e`](https://github.com/better-auth/better-auth/commit/a83152e2e884b1ac1724f95cea2056795d60e5cc) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Create new OAuth accounts in the user creation transaction. Adapters with native transaction support roll back the user when the account write fails, while other adapters still perform the writes sequentially. - [#&#8203;10128](https://github.com/better-auth/better-auth/pull/10128) [`97903c9`](https://github.com/better-auth/better-auth/commit/97903c9cca47f5fa62cf1d2ab86f6228db04aff0) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Preserve previously granted OAuth scopes across sign-in re-authentication and refresh-token requests. `account.scope` now accumulates monotonically: newly granted scopes are merged in only when added via `linkSocial`, and providers returning a narrower scope claim than the user has granted no longer shrink the stored value. - [#&#8203;10170](https://github.com/better-auth/better-auth/pull/10170) [`6ddb555`](https://github.com/better-auth/better-auth/commit/6ddb5554c01da4df6c637013dac7ea4ec8a43b52) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Bundled dependencies were refreshed to their latest compatible releases, including jose, nanostores, the noble crypto packages, and SimpleWebAuthn. These updates are backward compatible and require no changes to existing projects. - [#&#8203;10390](https://github.com/better-auth/better-auth/pull/10390) [`0de88f5`](https://github.com/better-auth/better-auth/commit/0de88f5e61d96f460e02b8a526e58acb16455d15) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - SCIM connections can now provision Users, Groups, and direct memberships into application-defined provisioning domains without the organization or SSO plugins. Applications can map Group membership to validated custom roles through projections. The service also supports SCIM 2.0 discovery, filtering, pagination, response attribute selection, atomic PATCH operations, and common request patterns used by Microsoft Entra ID and Okta. This replaces the previous SCIM configuration, client APIs, database schema, and organization-backed Group model. Existing SCIM installations cannot migrate provisioning state in place. Follow the SCIM cutover in the 1.7 upgrade guide, including full directory reprovisioning, before resuming traffic. Deferred database side effects now run only after a successful transaction. A rolled-back User update no longer refreshes its cached profile, and a rolled-back bulk session revocation no longer invalidates sessions. - [#&#8203;10505](https://github.com/better-auth/better-auth/pull/10505) [`d701f90`](https://github.com/better-auth/better-auth/commit/d701f90e6f81ede26209a50a5100bd9914a7ad5a) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - One Tap, Electron, and Expo client plugins now compose with `createAuthClient` without TypeScript errors, and the resulting client preserves each plugin's inferred actions. - [#&#8203;10621](https://github.com/better-auth/better-auth/pull/10621) [`59c4c83`](https://github.com/better-auth/better-auth/commit/59c4c832fc4eed813e98b5b2c45a91cf2d4ad9e7) Thanks [@&#8203;gustavovalverde](https://github.com/gustavovalverde)! - Allow test instances to enable native database transactions for postgres and mysql. - Updated dependencies \[[`5c45abc`](https://github.com/better-auth/better-auth/commit/5c45abcd2094d4a430cc84af6f9719fa0515ad71), [`763a267`](https://github.com/better-auth/better-auth/commit/763a2671c5372d88c291881977c8a1c2e29034b1), [`5d38b13`](https://github.com/better-auth/better-auth/commit/5d38b138c3c73eb06fe247ef6631c66e86ccc92b), [`692b22c`](https://github.com/better-auth/better-auth/commit/692b22c517011444f812fe21c206e399e35e8417), [`ea06c5a`](https://github.com/better-auth/better-auth/commit/ea06c5a71f448dfc600f1c2f7b0de732730c79cd), [`3d04fab`](https://github.com/better-auth/better-auth/commit/3d04fababbf3efd4c46a4012f46ed9397715c2e3), [`430c895`](https://github.com/better-auth/better-auth/commit/430c89549060ef6bd477ed2510650b9e49bba560), [`de8394d`](https://github.com/better-auth/better-auth/commit/de8394de207bae2fe9d0b8d7e901a196c1dc08d0), [`dbd302e`](https://github.com/better-auth/better-auth/commit/dbd302e422c66620cde391f6a80ab90ee34182f9), [`8784c1c`](https://github.com/better-auth/better-auth/commit/8784c1c1f4301acf96d980e5bf81ff56435e2545), [`ecd83da`](https://github.com/better-auth/better-auth/commit/ecd83daa01ec482d31667019737cb6697f03da0b), [`e4818b5`](https://github.com/better-auth/better-auth/commit/e4818b545984dce99e3c798ead5691c5bf775a70), [`7fe0e2b`](https://github.com/better-auth/better-auth/commit/7fe0e2b165c17207a43863b0f1c12c401976d6b2), [`0683a5f`](https://github.com/better-auth/better-auth/commit/0683a5f36befb45ade3866c7f8057791eadeee59), [`e7eb45b`](https://github.com/better-auth/better-auth/commit/e7eb45b065903f5fccddae491696cb069814a3c8), [`7c7313c`](https://github.com/better-auth/better-auth/commit/7c7313c8189baabd11a2ecb681bd2b16eb40fa4d), [`aedcb97`](https://github.com/better-auth/better-auth/commit/aedcb974f055c3514fe0464dc53d71d45a8a1725), [`03e6c94`](https://github.com/better-auth/better-auth/commit/03e6c94e965a7e87c1d44074b8e90257cb1f1cd2), [`4f53b61`](https://github.com/better-auth/better-auth/commit/4f53b61f49b470a40ccab18fe1fe4d80f225905f), [`2196ea6`](https://github.com/better-auth/better-auth/commit/2196ea65e724830d9f1066c6593210579de586b9), [`91f235f`](https://github.com/better-auth/better-auth/commit/91f235f8604cd432749adf18c7bd7d658aa1519b), [`34558bc`](https://github.com/better-auth/better-auth/commit/34558bc52b0e043021a1072f78de5f5439ae1734), [`1e5b808`](https://github.com/better-auth/better-auth/commit/1e5b80847208cf839c9d45363ca19b8eab41c68a), [`93d3871`](https://github.com/better-auth/better-auth/commit/93d3871bd2f7c2fdd423c4c88a22a50b6333e656), [`97903c9`](https://github.com/better-auth/better-auth/commit/97903c9cca47f5fa62cf1d2ab86f6228db04aff0), [`ed61b47`](https://github.com/better-auth/better-auth/commit/ed61b4798e0ccedadc3b0c0e0a2d08b5d4b7ed5a), [`0de88f5`](https://github.com/better-auth/better-auth/commit/0de88f5e61d96f460e02b8a526e58acb16455d15), [`3a79aff`](https://github.com/better-auth/better-auth/commit/3a79aff58ed82e45caf04c2ee4acaf0f4d09a86c), [`41cca60`](https://github.com/better-auth/better-auth/commit/41cca606d14e7b8a1d16da662d644ca39fe4281f), [`d701f90`](https://github.com/better-auth/better-auth/commit/d701f90e6f81ede26209a50a5100bd9914a7ad5a)]: - [@&#8203;better-auth/core](https://github.com/better-auth/core)@&#8203;1.7.0 - [@&#8203;better-auth/drizzle-adapter](https://github.com/better-auth/drizzle-adapter)@&#8203;1.7.0 - [@&#8203;better-auth/mongo-adapter](https://github.com/better-auth/mongo-adapter)@&#8203;1.7.0 - [@&#8203;better-auth/kysely-adapter](https://github.com/better-auth/kysely-adapter)@&#8203;1.7.0 - [@&#8203;better-auth/memory-adapter](https://github.com/better-auth/memory-adapter)@&#8203;1.7.0 - [@&#8203;better-auth/prisma-adapter](https://github.com/better-auth/prisma-adapter)@&#8203;1.7.0 - [@&#8203;better-auth/telemetry](https://github.com/better-auth/telemetry)@&#8203;1.7.0 ### [`v1.6.30`](https://github.com/better-auth/better-auth/blob/HEAD/packages/better-auth/CHANGELOG.md#1630) [Compare Source](https://github.com/better-auth/better-auth/compare/v1.6.29...v1.6.30) ##### Patch Changes - Updated dependencies \[[`07c1718`](https://github.com/better-auth/better-auth/commit/07c17189f58502bf038e5f22766f8a99df60ac99)]: - [@&#8203;better-auth/core](https://github.com/better-auth/core)@&#8203;1.6.30 - [@&#8203;better-auth/drizzle-adapter](https://github.com/better-auth/drizzle-adapter)@&#8203;1.6.30 - [@&#8203;better-auth/kysely-adapter](https://github.com/better-auth/kysely-adapter)@&#8203;1.6.30 - [@&#8203;better-auth/memory-adapter](https://github.com/better-auth/memory-adapter)@&#8203;1.6.30 - [@&#8203;better-auth/mongo-adapter](https://github.com/better-auth/mongo-adapter)@&#8203;1.6.30 - [@&#8203;better-auth/prisma-adapter](https://github.com/better-auth/prisma-adapter)@&#8203;1.6.30 - [@&#8203;better-auth/telemetry](https://github.com/better-auth/telemetry)@&#8203;1.6.30 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4yLjEiLCJ1cGRhdGVkSW5WZXIiOiI0NC4yLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImRlcGVuZGVuY2llcyJdfQ==-->
chore(deps): update dependency better-auth to v1.6.30
All checks were successful
Server CI / lint-and-build (pull_request) Successful in 2m9s
Portfolio CI / lint-and-build (pull_request) Successful in 2m11s
Web CI / lint-and-build (pull_request) Successful in 2m59s
Portfolio CI / build-and-publish (pull_request) Successful in 3m11s
Web CI / build-and-publish (pull_request) Successful in 2m37s
Server CI / build-and-publish (pull_request) Successful in 3m8s
3ff9eb5092
renovatebot force-pushed renovate/all-minor-patch from 3ff9eb5092
All checks were successful
Server CI / lint-and-build (pull_request) Successful in 2m9s
Portfolio CI / lint-and-build (pull_request) Successful in 2m11s
Web CI / lint-and-build (pull_request) Successful in 2m59s
Portfolio CI / build-and-publish (pull_request) Successful in 3m11s
Web CI / build-and-publish (pull_request) Successful in 2m37s
Server CI / build-and-publish (pull_request) Successful in 3m8s
to 47f3b1ead1
All checks were successful
Portfolio CI / lint-and-build (pull_request) Successful in 2m14s
Web CI / lint-and-build (pull_request) Successful in 2m23s
Portfolio CI / build-and-publish (pull_request) Successful in 3m34s
Web CI / build-and-publish (pull_request) Successful in 1m48s
Server CI / lint-and-build (pull_request) Successful in 1m12s
Server CI / build-and-publish (pull_request) Successful in 2m1s
2026-08-18 01:10:02 +00:00
Compare
renovatebot changed title from chore(deps): update dependency better-auth to v1.6.30 to chore(deps): update dependency better-auth to v1.7.0 2026-08-18 01:10:05 +00:00
All checks were successful
Portfolio CI / lint-and-build (pull_request) Successful in 2m14s
Web CI / lint-and-build (pull_request) Successful in 2m23s
Portfolio CI / build-and-publish (pull_request) Successful in 3m34s
Web CI / build-and-publish (pull_request) Successful in 1m48s
Server CI / lint-and-build (pull_request) Successful in 1m12s
Server CI / build-and-publish (pull_request) Successful in 2m1s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin renovate/all-minor-patch:renovate/all-minor-patch
git switch renovate/all-minor-patch

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff renovate/all-minor-patch
git switch renovate/all-minor-patch
git rebase main
git switch main
git merge --ff-only renovate/all-minor-patch
git switch renovate/all-minor-patch
git rebase main
git switch main
git merge --no-ff renovate/all-minor-patch
git switch main
git merge --squash renovate/all-minor-patch
git switch main
git merge --ff-only renovate/all-minor-patch
git switch main
git merge renovate/all-minor-patch
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
faceguy/mosaic!28
No description provided.