Better Auth: OAuth callback accepts mismatched `state` when cookie-backed state storage is used without PKCE
Date: 2026-05
Affected: npm/better-auth
Attack vector: auth-bypass
Category: vulnerability-disclosure
Severity: Medium
CWE: CWE-287
CWE-345
CWE-352
CVSS: 5.3 (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N)

### Am I affected? Users are affected if all of the following are true: - The application uses `better-auth` at a version below `1.6.2` (or `@better-auth/sso` paired with such a version). - `betterAuth({ account: { storeStateStrategy } })` is set to `"cookie"`. The default `"database"` is not affected. - The application wires at least one OAuth provider through `genericOAuth({ config })` with `pkce: false`, or it supplies a custom `getToken` or `tokenUrl` that does not require the stored `codeVerifier`. Stock social providers with PKCE on are not affected. - The provider returns arbitrary `code` values to the configured callback URL. If users are on `better-auth@1.6.2` or later, they are not affected. Fix: 1. Upgrade to `better-auth@1.6.2` or later (current stable is `1.6.10`). 2. If users cannot upgrade, see workarounds below. ### Summary 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 round-trip), an attacker who forced the victim to deliver an attacker-controlled authorization code to the callback would mint a session bound to


Mitigations:

Architectural layers (MAESTRO):