npm better-auth 1.8.0-beta.10
v1.8.0-beta.10

55 minutes ago

Important

Action required

  • If you use a read replica for your verification store, make sure it reads its own writes, because every setup now writes a verification row per revoked grant and a lagging replica can let in-flight requests keep their tokens. (#12581)
  • If you want requireMcpAuth to keep verifying JWT signatures alone instead of checking the provider's records, set jwksUrl. (#12597)
  • If you have a grant extension that passes act to issueTokens, set sub_profile: "service" on client actors, or pairwise clients will see those client IDs as pairwise values. (#12584)
  • If you have a may_act that names a service by sub, add sub_profile: "service" so it keeps its client ID. (#12584)
  • If you return undefined from customAccessTokenClaims or other claim sources to remove a claim set elsewhere, return a value such as null instead, because the lower-priority value now stays. (#12575)
  • If you have a custom clientAuthentication strategy that compares aud with expectedAudience, move it to acceptedAudiences and pass that to consumeClientAssertion. (#12599)
  • If you have clients that sign client assertions for an endpoint URL, update them to use the issuer as aud, or set assertionAudience: "issuer-or-endpoint" temporarily to keep accepting them. (#12599)
  • If your authorization server accepts only its token endpoint URL as the assertion audience, set audience: "token_endpoint". (#12599)
  • If you sign in to a Better Auth 1.8 authorization server with private_key_jwt, set oidcConfig.clientAssertionAudience: "issuer". (#12599)
  • If you send UserInfo requests with an Authorization header that has no Bearer or DPoP scheme, send the token as Authorization: Bearer <token>. If you handle the old 400 invalid_scope response, handle 403 insufficient_scope instead. (#12605)
  • If your provider publishes discovery at a non-well-known path or under another name for the same issuer, set issuer to the issuer identifier the provider documents. If its discovery document has no issuer, configure authorizationUrl, tokenUrl, and userInfoUrl instead of discoveryUrl. (#12585)
  • If you set ba_sub in customAccessTokenClaims, an extension, or a resource's customClaims, rename your claim. (#12577)
  • If you call requireActiveAccessToken and then enforceDpopBinding on an endpoint, replace both calls with provider.verifyResourceRequest(), which checks the DPoP proof. (#12606)
  • If you pass authorization_details_types_supported to getProtectedResourceMetadata, wrap the value in an array. (#12513)
  • If you match body.error === "invalid_token" to catch a missing or unsupported Authorization scheme, check for a 401 without body.error instead. (#12512)
  • If you have exhaustive checks over DpopBindingErrorCode or DpopProofErrorCode, add a case for use_dpop_nonce so the code typechecks. (#12511)
  • If you use CIBA, run npx auth@latest migrate, or npx auth@latest generate and apply the schema with your ORM, before you upgrade to add the acrValues and authorizationDetails columns to the cibaRequest model. (#12509)
  • If you use the OAuth provider, add the requirePushedAuthorizationRequests column to the oauthClient table before deploying, even with PAR disabled, because requests fail until it exists: run npx auth@latest migrate with the built-in Kysely adapter, or npx auth@latest generate and apply the schema with your ORM. (#12518)
  • If you use the OAuth provider, run npx auth@latest migrate before upgrading, or run npx auth@latest generate and apply the schema with your ORM's migration tool, to add the accessTokenClaims column to oauthAccessToken. (#12508)
  • If you use oauthProvider(), before deploying run npx auth@latest migrate with the built-in Kysely adapter, or run npx auth@latest generate and apply the schema with your ORM, to add the grantId columns to oauthRefreshToken and oauthAccessToken. (#12520)
  • If you have a custom OAuthProviderApi implementation, add the challengeDpopNonce() method. (#12522)

better-auth

❗ Breaking Changes

  • Changed createPrivateKeyJwtClientAssertionGetter to sign client assertions for the authorization server's issuer by default when the issuer is known (#12599)
  • Changed Generic OAuth to check the discovery document's issuer against discoveryUrl, skipping providers whose issuer does not match (#12585)

Features

  • Added createDpopFetch in better-auth/oauth2/dpop-client for sending DPoP-bound requests with per-origin nonce handling and a single retry on use_dpop_nonce (#12506)
  • Added endpointType to custom token endpoint authentication hooks, and issuer to hooks and client assertion getters when Better Auth knows it (#12580)
  • Added a dpop: { keyPair } option so Generic OAuth providers can request DPoP-bound tokens (RFC 9449) (#12596)
  • Added support for pushed authorization requests (RFC 9126) in Generic OAuth sign-in through the new pushedAuthorizationRequest option (#12519)

Bug Fixes

  • Added deriveEncryptionKey(secret, purpose) to better-auth/crypto so plugins can derive a separate encryption key for each value they encrypt (#12577)

For detailed changes, see CHANGELOG

@better-auth/oauth-provider

❗ Breaking Changes

  • Deprecated the expectedAudience strategy input and parameter in favor of acceptedAudiences for clientAuthentication strategies and consumeClientAssertion (#12599)
  • Changed client assertions such as private_key_jwt to require the provider's issuer as their only aud value, rejecting endpoint URLs with invalid_client (#12599)
  • Changed access-token claims set to undefined to be left out instead of removing a same-named claim from a lower-priority source (#12575)
  • Changed UserInfo to return 403 insufficient_scope for access tokens without the openid scope and to reject Authorization headers without a Bearer or DPoP scheme (#12605)
  • Changed requireActiveAccessToken to refuse DPoP-bound access tokens with a 401 invalid_token and a DPoP challenge instead of accepting them without checking a proof (#12606)

Features

  • Changed token exchange to reject a subject token that carries authorization_details with invalid_request (#12509)
  • Added opt-in token exchange for DPoP-bound public clients through oauthTokenExchange({ publicClients: "dpop-bound" }) (#12517)
  • Added a pushed authorization request endpoint (POST /oauth2/par, RFC 9126) to the OAuth provider, with optional global or per-client enforcement (#12518)
  • Added a clientId option to provider.authenticateClient() so plugin endpoints can authenticate follow-up requests that omit client_id (#12529)
  • Added an expectedJkt option to provider.verifyDpopProof() so plugin endpoints can require a later request to use the same DPoP key as an earlier one (#12529)
  • feat(oauth-provider): bring DPoP challenges in line with Bearer (#12579)
  • Added the ability for adminUpdateOAuthClient to grant or revoke RP-initiated logout on any client, including ownerless ones, by sending only enable_end_session (#12588)
  • Fixed updateOAuthClient to reject new post_logout_redirect_uris entries on another origin than the client's redirect_uris, for clients that may use RP-initiated logout (#12588)
  • Changed OAuth provider discovery to publish combined, de-duplicated lists when multiple extensions advertise the same list, such as authorization_details_types_supported (#12564)
  • Added issueAuthorizationCode to the OAuth provider API so plugin endpoints can issue authorization codes for a signed-in user without a redirect (#12565)
  • Changed /oauth2/authorize and /oauth2/par to reject requests with authorization_details using invalid_authorization_details when discovery advertises supported types (#12591)
  • Added support for extension grants to issue RFC 9396 authorization_details through issueTokens in token responses, JWT access tokens, and introspection (#12591)
  • Added challengeDpopNonce() so plugin endpoints and before hooks can issue a nonce-only DPoP challenge before staging state (#12522)
  • Improved OAuth provider extension conflict errors and warnings to name the extensions involved, with an optional name for extensions (#12646)
  • Added opt-in revocation of JWT access tokens at /oauth2/revoke with revocableJwtAccessTokens: true (#12507)
  • Added resolveSubject({ userId, client }) to the provider API so grant extensions and companion plugins can get the sub a client sees for a user (#12505)
  • Added opt-in pairwise sub in JWT access tokens for pairwise clients with accessTokenSubject: "pairwise", so decoding a token no longer reveals the user ID (#12505)
  • Added a clientRegistrationEnableEndSession option to allow RP-initiated logout at /oauth2/end-session for dynamically registered and other registered OAuth clients (#12510)
  • Fixed introspection of delegated access tokens to show pairwise clients the pairwise sub of each user in act instead of user IDs (#12523)
  • Added accessTokenClaims from issueTokens to opaque access token introspection, so custom grants now see those claims (#12508)
  • Added sub_profile to each actor in a delegated token's act claim, marking clients as "service" and users as "user" (#12584)
  • Changed token exchange to deny subject tokens whose may_act names no actor, with invalid_request, instead of allowing any actor (#12584)
  • Fixed introspection and pairwise JWT access tokens exposing the real user ID in may_act.sub to pairwise clients (#12584)
  • Added revocation of a grant's JWT access tokens when its refresh token is revoked or a code or rotated token is replayed, with revocableJwtAccessTokens (#12520)
  • Changed refresh token revocation at /oauth2/revoke to delete every opaque access token of its grant, including those issued before earlier refreshes (#12520)
  • Added verifyResourceRequest to verify JWT and opaque access token requests in-process on plugin endpoints served by the auth server (#12516)
  • Added a warning when discovery ignores a metadata contribution from an OAuth provider extension or client discovery (#12574)
  • Improved discovery metadata warnings to log once per server instead of on every request to the well-known endpoints (#12574)

Bug Fixes

  • Fixed authorization_details_types_supported in protected resource metadata to be typed as string[], matching RFC 9728 (#12513)
  • Changed the OAuth provider to reserve the ba_sub access token claim, dropping custom values for it with a warning (#12577)
  • Fixed token requests with an already-used DPoP proof consuming an unused authorization code or device code, so clients can retry with a fresh proof (#12600)
  • Fixed refresh token revocation and replay handling to retry refresh-token family teardown when a delete fails, such as after a MySQL deadlock (#12643)
  • Fixed authorization code reuse to revoke issued tokens only when the request comes from the original client, including its code_verifier for PKCE codes (#12526)
  • Fixed OAuth grant revocation to also stop token requests for that grant that are still issuing tokens (#12581)

For detailed changes, see CHANGELOG

@better-auth/core

❗ Breaking Changes

  • Changed createPrivateKeyJwtClientAssertionGetter to sign client assertions for the authorization server's issuer by default when the issuer is known (#12599)

Features

  • Added createDpopFetch in better-auth/oauth2/dpop-client for sending DPoP-bound requests with per-origin nonce handling and a single retry on use_dpop_nonce (#12506)
  • Added an issuer option to validateAuthorizationCode, refreshAccessToken, clientCredentialsToken, and pushAuthorizationRequest for client assertion getters (#12580)
  • Added endpointType to custom token endpoint authentication hooks, and issuer to hooks and client assertion getters when Better Auth knows it (#12580)
  • Added a fetch option to validateAuthorizationCode, refreshAccessToken, clientCredentialsToken, and pushAuthorizationRequest so requests can send DPoP proofs (#12596)
  • Added pushAuthorizationRequest to @better-auth/core/oauth2 for sending pushed authorization requests (RFC 9126) and getting the redirect URL (#12519)

Bug Fixes

  • Added use_dpop_nonce to the DpopBindingErrorCode and DpopProofErrorCode types exported from @better-auth/core/oauth2 (#12511)
  • Fixed verifyAccessTokenRequest to reject a missing or unsupported Authorization scheme without an invalid_token error, matching RFC 6750 (#12512)

For detailed changes, see CHANGELOG

@better-auth/mcp

❗ Breaking Changes

  • Changed requireMcpAuth to check access tokens against the OAuth provider's records when it runs in the same process, accepting opaque tokens and rejecting revoked ones (#12597)

For detailed changes, see CHANGELOG

@better-auth/sso

❗ Breaking Changes

  • Added oidcConfig.clientAssertionAudience so OIDC SSO providers using private_key_jwt can sign client assertions for the provider's registered issuer (#12599)

For detailed changes, see CHANGELOG

@better-auth/ciba

Features

  • Added support for acr_values and authorization_details in CIBA requests to /oauth2/bc-authorize (#12509)
  • Added authorization_details_types to client registration so CIBA rejects requests for unregistered authorization details types from clients that set the field (#12591)
  • Improved OAuth provider extension conflict errors and warnings to name the extensions involved, with an optional name for extensions (#12646)

Bug Fixes

  • Fixed CIBA approval links and the advertised backchannel_authentication_endpoint containing a double slash when baseURL ends in / (#12638)

For detailed changes, see CHANGELOG

@better-auth/cimd

Features

  • Fixed cimd() startup failing with a duplicate client discovery error when its init() runs more than once for the same oauthProvider() instance (#12646)
  • Improved OAuth provider extension conflict errors and warnings to name the extensions involved, with an optional name for extensions (#12646)

For detailed changes, see CHANGELOG

Contributors

Thanks to everyone who contributed to this release:

@gustavovalverde

Full changelog: v1.8.0-beta.9...v1.8.0-beta.10

Don't miss a new better-auth release

NewReleases is sending notifications on new releases.