Advisories for Npm/Better-Auth package

2026

Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows

When secondaryStorage is configured and storeSessionInDatabase is false, three user-deletion endpoints in better-auth plus one in @better-auth/scim call internalAdapter.deleteUser(userId) without first calling internalAdapter.deleteSessions(userId). The deleted user's session payload (which carries a cached user object) remains in secondary storage, and internalAdapter.findSession(token) keeps returning it as a valid session until the session TTL elapses (default 7 days).

Better Auth: OAuth refresh-token rotation forks the token family on concurrent redemption

The OAuth provider's POST /oauth2/token endpoint, on the refresh_token grant, performs a non-atomic read / validate / revoke / mint sequence on the oauthRefreshToken row. Two concurrent requests presenting the same parent refresh token both pass the revocation check before either revoke completes, so each mints a fresh refresh token. The replay-detection branch only fires when revoked is already truthy at read time, which is exactly the state concurrent attackers …

Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins

The legacy oidcProvider and mcp plugins each expose an OAuth 2.0 token endpoint whose refresh_token grant authenticates the request entirely on possession of the bound refreshToken row and a matching client_id. Neither plugin verifies the registered confidential client's client_secret on the refresh path. An attacker who obtains any valid refresh_token (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public client_id can mint …

Better Auth vulnerable to unauthorized invitation acceptance via unverified email match in organization plugin

The organization plugin's acceptInvitation endpoint trusts an email-string equality check as proof that the session user owns the invited address. With Better Auth's stock emailAndPassword: { enabled: true } configuration, requireEmailVerification defaults to false, so an attacker can sign up a row keyed to victim@target.example (auto-signed-in, emailVerified: false) before the legitimate owner. When an organization admin invites that address, the attacker presents the invitationId and accepts the invitation, joining the …

Better Auth has stored XSS in the auth-server origin via javascript: redirect_uri in oidc-provider and mcp

The deprecated oidc-provider plugin registers OAuth clients without validating the scheme of their redirect_uris. An attacker stores a javascript: URI as a client redirect target, and the authorization server later returns that URI to the browser in the consent response. A consent page that navigates to the returned value then executes attacker JavaScript in the authorization-server origin, which exposes the victim's session and enables account takeover. The mcp plugin wraps …

Better Auth has insecure cryptographic defaults in oidcProvider: alg=none advertised and plain PKCE accepted by default

The legacy oidcProvider and mcp plugins exhibit two related defects in their OIDC discovery and authorize surfaces. The discovery document advertises "none" in id_token_signing_alg_values_supported (and, for mcp, in resource_signing_alg_values_supported on the OAuth protected-resource metadata). Any relying party that performs algorithm negotiation from this metadata without pinning to a real signing algorithm may accept unsigned tokens. PKCE plain is enabled by default. The runtime gate in the authorize handler accepts code_challenge_method=plain …

Better Auth has an account takeover issue via OAuth auto-link to unverified pre-registered email

The OAuth callback's auto-link gate in handleOAuthUserInfo admits an implicit account link whenever the provider asserts email_verified: true, without requiring the local user row's emailVerified to also be true. An attacker who pre-registers a victim's email through /sign-up/email (which writes a row with emailVerified: false) can have the victim's later OAuth identity bound to the attacker's user row, granting both a password login and the victim's OAuth identity on the …

@better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive

The OAuth provider's POST /oauth2/token endpoint, on the authorization_code grant, redeems a single-use authorization code through a non-atomic find-then-delete sequence. Two concurrent requests with the same code value both pass the read step before either delete completes, then both proceed to PKCE verification and createUserTokens. Each surviving request mints a fresh access token, refresh token, and id token. RFC 6749 §4.1.2 requires authorization codes to be single-use; this primitive does …

Better Auth: Device authorization approve and deny accept any authenticated session while the user code is pending

Better Auth's deviceAuthorization plugin treated any authenticated session as the owner of any pending device code. The ownership gate on POST /device/approve and POST /device/deny short-circuited whenever the row's userId was unset, and the GET /device verification handler did not claim the row. An authenticated attacker who learned a valid user_code before the legitimate user completed approval could bind the polling device to the attacker's account or deny the legitimate …

Better Auth: Rate limiter keys IPv6 addresses individually and is bypassable via prefix rotation

Better Auth's HTTP rate limiter keyed each request by the exact textual IP address it received in x-forwarded-for (or the configured IP-bearing header). IPv6 clients controlling a typical /64 allocation could rotate through 2^64 distinct source addresses without exhausting the per-address counter, defeating rate limiting on /sign-in/email, /sign-up/email, /forget-password, and every other path the limiter protects. The same bug allowed a single client to vary the textual encoding of one …

Better Auth: OAuth callback accepts mismatched `state` when cookie-backed state storage is used without PKCE

In parseGenericState, the cookie branch decrypted the oauth_state cookie and validated expiry, but did not compare the incoming OAuth state query parameter to the nonce that generateGenericState issued at sign-in. Any callback to /api/auth/oauth2/callback/<providerId> that arrived with a forged state and any code was therefore accepted as long as the browser still held a live oauth_state cookie. With pkce: false (or any getToken path that does not enforce a code-verifier …

2025

Better Auth affected by external request basePath modification DoS

Affected versions of Better Auth allow an external request to configure baseURL when it isn’t defined through any other means. This can be abused to poison the router’s base path, causing all routes to return 404 for all users. This issue is only exploitable when baseURL is not explicitly configured (e.g., BETTER_AUTH_URL is missing) and the attacker is able to make the very first request to the server after startup. …

Better Auth's multi-session sign-out hook allows forged cookies to revoke arbitrary sessions

A vulnerability was identified in the multi-session plugin for Better Auth, specifically in the /sign-out after-hook. The hook trusts raw multi-session cookies and forwards the extracted values directly to internalAdapter.deleteSessions without verifying the cookie signature. Because cookie values are not validated with getSignedCookie (or any equivalent check), an attacker can supply a forged _multi-* cookie to trigger deletion of arbitrary session tokens.

Better Auth: Unauthenticated API key creation through api-key plugin

A critical authentication bypass was identified in the API key creation and update endpoints. An attacker could create or modify API keys for arbitrary users by supplying a victim’s user ID in the request body. Due to a flaw in how the authenticated user was derived, the endpoints could treat attacker-controlled input as an authenticated user object under certain conditions.

Better Auth allows bypassing the trustedOrigins Protection which leads to ATO

A bypass was discovered in the trustedOrigins validation logic—affecting both absolute URL entries and wildcard domain patterns. This flaw allows an attacker to construct a malicious callbackURL that passes origin checks and triggers an open redirect. Because redirect endpoints include sensitive tokens (such as password-reset tokens), this vulnerability can enable one-click account takeover if a victim clicks a crafted link.

Beter Auth has an Open Redirect via Scheme-Less Callback Parameter

The application is vulnerable to an open redirect due to improper validation of the callbackURL parameter in the email verification endpoint and any other endpoint that accepts callback url. While the server blocks fully qualified URLs (e.g., https://evil.com), it incorrectly allows scheme-less URLs (e.g., //malicious-site.com). This results in the browser interpreting the URL as https://malicious-site.com, leading to unintended redirection. bypass for : https://github.com/better-auth/better-auth/security/advisories/GHSA-8jhw-6pjj-8723

2024