CVE-2026-53518: @better-auth/oauth-provider Authorization Code Race Condition Allows Duplicate Token Issuance
A race condition in the OAuth authorization-code token endpoint lets two concurrent requests redeem the same single-use code simultaneously, each receiving valid but independent access, refresh, and I
The problem
The `POST /oauth2/token` endpoint on the `authorization_code` grant used a non-atomic find-then-delete sequence to redeem single-use authorization codes. Two concurrent requests with the same `code` value both read the verification row before either delete completed, so both passed PKCE verification and reached `createUserTokens`.
Each surviving request minted a fully independent access token, refresh token, and ID token for the user. This violates RFC 6749 §4.1.2's single-use requirement and produces forked, live token sets that are invisible to standard single-use enforcement. The same flawed primitive was shared by the legacy `oidc-provider` and `mcp` plugins, so all three token endpoints were affected.
Proof of concept
A working proof-of-concept for CVE-2026-53518 in @better-auth/oauth-provider, with the exact payload below.
# Send both requests simultaneously (e.g. with curl --parallel or two threads).
# Replace CODE, CLIENT_ID, VERIFIER, and REDIRECT_URI with real values from a live auth flow.
curl -s -X POST https://your-app.example/api/auth/oauth2/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&code=CODE&client_id=CLIENT_ID&code_verifier=VERIFIER&redirect_uri=REDIRECT_URI' &
curl -s -X POST https://your-app.example/api/auth/oauth2/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&code=CODE&client_id=CLIENT_ID&code_verifier=VERIFIER&redirect_uri=REDIRECT_URI' &
wait
# On a vulnerable server both requests return HTTP 200 with distinct access_token values.The root cause is CWE-367 (TOCTOU): the handler called `findVerificationByIdentifier(code)`, then separately called `deleteVerificationByIdentifier(code)`. Because `deleteVerificationByIdentifier` returned `Promise<void>`, the delete count was discarded and no call site could detect that another concurrent caller had already claimed the row.
The window between the read and the delete was wide enough to admit a second request under normal async Node.js scheduling.
The patch introduces `internalAdapter.consumeVerificationValue(identifier)`, which performs an atomic delete-and-return in a single database operation. Each adapter implements this natively (memory, Mongo, Drizzle, Kysely, Prisma) or falls back to a `transaction(findMany + delete)` pair.
The first caller gets the row and mints tokens; every concurrent racer gets `null` and receives `invalid_grant`. Error-code consistency was also corrected: the non-standard `invalid_verification` error code was replaced with the RFC 6749 §5.2-compliant `invalid_grant` on all malformed-value branches.
The fix
Upgrade to `@better-auth/oauth-provider@1.6.11` or `better-auth@1.6.11` (covers the legacy `oidc-provider` and `mcp` plugin paths). No configuration change is needed. The new atomic primitive is a drop-in replacement at the internal-adapter layer. If an immediate upgrade is not possible, the advisory describes partial network-layer or database-layer mitigations, but none fully close the race without the code patch.
Reported by chdanielmueller.
Related research
- high · 8.1CVE-2026-53517CVE-2026-53517: @better-auth/oauth-provider Refresh Token Family Fork via Race Condition
- high · 8.5CVE-2026-54353CVE-2026-54353: @budibase/backend-core SSRF DNS Rebinding Bypass
- high · 7.7CVE-2026-53514CVE-2026-53514: better-auth Organization Invitation Takeover via Unverified Email Pre-Registration
- high · 8.3CVE-2026-53516CVE-2026-53516: better-auth OAuth Auto-Link Account Takeover via Unverified Email