Overview
The OAuth refresh-token rotation vulnerability in Better Auth is a critical issue that affects applications using the `@better-auth/oauth-provider` library at versions `>= 1.6.0, < 1.6.11` or the embedded plugin in `better-auth >= 1.4.8-beta.7, < 1.6.0`. This vulnerability is particularly concerning because it allows an attacker to gain indefinite access to a user's account if they can steal a refresh token.
Understanding the Vulnerability / Threat
Root Cause Analysis
The root cause of this vulnerability lies in the non-atomic read/validate/revoke/mint sequence on the `oauthRefreshToken` row in the `POST /oauth2/token` endpoint during the `refresh_token` grant. Specifically, the `adapter.update` predicate only checks the `id` and not `revoked IS NULL`, allowing two concurrent updates to succeed. This results in a forked refresh-token family from a single parent token.
This issue belongs to the CWE-362 category, which involves concurrent execution using shared resources with improper synchronization, also known as a race condition.
Attack Surface & Vector
The attack surface for this vulnerability is significant because it can be exploited under certain conditions:
- The project must depend on `@better-auth/oauth-provider` at a version `>= 1.6.0, < 1.6.11` or use the embedded plugin in `better-auth >= 1.4.8-beta.7, < 1.6.0`.
- At least one OAuth client must request the `offline_access` scope, which mints refresh tokens.
- Concurrent redemption of the same refresh token must be reachable, which can happen in various scenarios such as an SPA sharing a refresh token across browser tabs without a mutex or an attacker timing two requests with a stolen refresh token.
Exploitation Mechanics โ Scenario Walkthrough
Scenario: Compromising a Corporate Jenkins Instance
1.
Initial Position: An attacker steals a refresh token from a user's session in a corporate environment where the application uses Better Auth for OAuth.
2.
Triggering the Flaw: The attacker crafts two concurrent HTTP POST requests to `/oauth2/token` with the stolen refresh token, exploiting the non-atomic check in the refresh-token rotation logic.
3.
What Breaks: Both requests pass the revocation check before either revokes the token, resulting in two fresh refresh tokens being minted. The security boundary fails because the check for `revoked` is not atomic with the consumption step.
4.
Attacker's Prize: The attacker gains indefinite access to the user's account at the original authorization scope. Even if one token is revoked, the other remains valid, allowing continued access.
Real-World Impact
The impact of this vulnerability is severe. An attacker can achieve:
- **Indefinite access** from a single stolen refresh token, as forked refresh-token families grant access at the original user's authorization scope.
- **Detection bypass**, as legitimate users with forked refresh tokens do not trip family invalidation when they refresh.
Detection & Defense
Immediate Mitigations
1. **Upgrade to `@better-auth/
[email protected]` or later**.
2. If an immediate upgrade is not possible, apply workarounds such as configuring the database adapter to run under serializable isolation or wrapping the `adapter.update` with a row-level pessimistic lock.
Detection Strategies
Defenders can detect exploitation attempts by monitoring for:
- Unusual patterns of refresh token usage.
- Multiple concurrent requests to the `/oauth2/token` endpoint with the same refresh token.
Long-Term Hardening
- Implement strict refresh-token family invalidation on detected reuse, as prescribed by RFC 9700 ยง4.14.
- Enforce atomic compare-and-swap operations in the rotation primitive.
- Add a unique constraint on `oauthRefreshToken.token` for defense-in-depth.
Key Takeaways
- Understand the race condition in the OAuth refresh-token rotation mechanism.
- Recognize the scenarios where concurrent redemption of refresh tokens can be exploited.
- Implement immediate mitigations and long-term hardening strategies to prevent exploitation.
Sources
- [GitHub Security Advisories: GHSA-392p-2q2v-4372](https://github.com/advisories/GHSA-392p-2q2v-4372)
- [CVE-2026-53517](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-53517)