May 15, 2026·6 min read·3 visits
Better Auth fails to verify the OAuth state parameter during callback processing when using the cookie storage strategy without PKCE, enabling Login CSRF attacks.
Better Auth's OAuth implementation contains a logic flaw in its handling of the state parameter when utilizing the cookie-backed state storage strategy. The application fails to cryptographically bind the generated OAuth state nonce to the stored session metadata, leading to insufficient verification during the callback phase. This omission permits Login Cross-Site Request Forgery (CSRF) and account association attacks when Proof Key for Code Exchange (PKCE) is disabled.
Better Auth provides authentication solutions for web applications, including comprehensive OAuth 2.0 integrations. The framework supports multiple state storage mechanisms to accommodate different application architectures. One of these mechanisms is a cookie-backed strategy configured via storeStateStrategy: "cookie".
The vulnerability resides in the OAuth 2.0 callback processing mechanism. When configured with the cookie storage strategy, the application fails to adequately validate the state parameter returned by the identity provider's authorization server. The validation logic checks the integrity of the cookie but ignores the incoming state value.
This omission breaks the primary CSRF protection mechanism specified by the OAuth 2.0 standard. Attackers exploit this by forging authentication responses and forcing victim browsers to process attacker-controlled authorization codes. The application accepts the code and executes the authentication flow in the victim's session.
Exploitation requires specific environmental and user conditions. The victim must possess an active, unexpired oauth_state cookie generated by initiating a legitimate authentication attempt. The target application must also be operating without Proof Key for Code Exchange (PKCE) enabled.
The OAuth 2.0 standard dictates that the state parameter acts as a nonce to bind the authorization request to the specific user agent initiating the flow. The client application must verify that the state returned in the callback exactly matches the state issued in the initial authorization request. This verification prevents CSRF attacks.
During the initial authorization phase, Better Auth generates a state nonce and encrypts session metadata into an oauth_state cookie. This metadata includes configuration details such as the callback URL and expiration time. However, the generated state nonce is omitted from the encrypted payload.
During the callback phase, the parseGenericState function decrypts the oauth_state cookie to retrieve the stored metadata. The application checks the cookie's integrity and verifies that the expiration timestamp is valid. It successfully retrieves the necessary context to continue the OAuth flow.
The critical flaw occurs during the state verification step. The application reads the state query parameter from the callback URL but performs no comparison against the original nonce. Because the original nonce was never stored in the cookie, the framework blindly accepts any state value provided in the request, provided the cookie itself is valid.
Prior to the patch, the state generation process populated the cookie with metadata but discarded the actual state nonce. The parsing logic subsequently decrypted the cookie and returned the metadata without possessing a reference value to verify the incoming state parameter against. This created a structural blind spot in the authentication flow.
Commit 9deb7936aba7931f2db4b460141f476508f11bfd resolves this flaw by explicitly binding the state nonce to the session data. The generateGenericState function now injects oauthState: state into the data structure before applying encryption. This ensures the reference value travels securely with the session metadata.
The patch introduces strict verification logic within the parseGenericState function. The code extracts the oauthState value from the decrypted cookie and performs an exact equality check against the incoming state parameter. If the values diverge, the application halts the flow:
if (!parsedData.oauthState || parsedData.oauthState !== state) {
throw new StateError("State mismatch: OAuth state parameter does not match stored state", {
code: "state_security_mismatch",
details: { state },
});
}The fix extends protection to the broader proxy routing logic. The oAuthProxy is updated to enforce the cryptographic binding. It validates that the newly extracted stateData.oauthState perfectly matches the statePackage.state retrieved from the incoming HTTP request.
The attack sequence begins with the attacker initiating an OAuth flow on their own device against the target application. The attacker authenticates with the identity provider and captures the resulting callback URL. This URL contains a valid authorization code and an associated state parameter generated for the attacker's session.
The attacker deliberately halts their authentication flow, preventing their own client from consuming the captured authorization code. The attacker then constructs a malicious link using the target application's callback endpoint. This link incorporates the attacker's captured authorization code and arbitrary state parameter.
The attacker delivers this crafted link to the victim. The victim must possess a valid, unexpired oauth_state cookie for the target application. This cookie is naturally generated if the victim recently initiated an OAuth sign-in flow, even if they abandoned the process before completion.
When the victim's browser accesses the crafted link, the target application receives the request. The application reads the victim's valid cookie, bypasses the state verification, and processes the attacker's authorization code. The application authenticates the victim as the attacker or binds the attacker's external identity to the victim's profile.
The primary consequence of this vulnerability is Login Cross-Site Request Forgery (Login CSRF). An attacker forces a victim to log into the application under the attacker's account context. The victim unknowingly interacts with the application utilizing the attacker's profile and settings.
Login CSRF introduces severe data leakage and privacy risks. If the victim inputs sensitive information while operating under the attacker's account, the attacker retains full access to that data. This encompasses payment details, personal identification information, or proprietary documents uploaded during the session.
The vulnerability enables account association attacks in specific application configurations. If the application permits linking multiple identity providers to a single internal account, the flaw allows an attacker to bind their own identity provider account to the victim's application account. The attacker then secures persistent, unauthorized access to the victim's actual profile.
The vulnerability carries a CVSS v3.1 base score of 6.5, categorizing it as medium severity. The requirement for user interaction and the prerequisite of an active oauth_state cookie constrain the attack surface. Furthermore, applications utilizing the database storage strategy or enforcing PKCE are structurally immune.
The definitive remediation involves upgrading the better-auth library to a version incorporating commit 9deb7936aba7931f2db4b460141f476508f11bfd. This patch enforces strict state verification during the callback parsing phase. Upgrading eliminates the vulnerability without requiring architectural changes.
Configuration adjustments provide effective workarounds for environments where immediate patching is unfeasible. Administrators must alter the storeStateStrategy configuration directive. Transitioning to the database strategy relies on primary key lookups rather than client-side cookies, inherently mitigating this specific CSRF vector.
Implementing Proof Key for Code Exchange (PKCE) offers a robust secondary defense layer. PKCE binds the authorization code to a specific client session via a cryptographically secure verifier generated during the initial request. Enabling PKCE via pkce: true prevents the attacker from successfully injecting an authorization code generated in a disparate session.
Security and operations teams should monitor authentication logs for anomalous patterns. The presence of state_security_mismatch errors indicates the application successfully blocked an invalid state transition. A sudden influx of these errors from distinct IP addresses signals potential exploitation attempts or systemic misconfigurations.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
better-auth Better Auth | < 9deb7936aba7931f2db4b460141f476508f11bfd | Post-commit 9deb7936aba7931f2db4b460141f476508f11bfd |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-352, CWE-345 |
| Attack Vector | Network |
| CVSS Base Score | 6.5 |
| Impact | Login CSRF / Account Hijacking |
| Exploit Status | Proof of Concept Available |
| Authentication Required | None |
The application fails to sufficiently verify the authenticity of data, allowing an attacker to execute unintended actions on behalf of the user.