CVE-2026-53518
Better Auth OAuth Provider: Race Condition in Authorization Code Exchange Enables Multi-Use Code Redemption
Better Auth is an authentication and authorization library for TypeScript. From 1.6.0 until 1.6.11, the @better-auth/oauth-provider POST /oauth2/token endpoint for the authorization_code grant redeems a single-use authorization code through a non-atomic find-then-delete sequence, allowing two concurrent requests to pass the read step and mint independent access tokens, refresh tokens, and ID tokens; legacy /oauth2/token and /mcp/token paths in oidc-provider and mcp plugins share the same primitive. This issue is fixed in version 1.6.11.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.6
Better Auth OAuth Provider: Race Condition in Authorization Code Exchange Enables Multi-Use Code Redemption
Better Auth is an authentication and authorization library for TypeScript. From 1.6.0 until 1.6.11, the @better-auth/oauth-provider POST /oauth2/token endpoint for the authorization_code grant redeems a single-use authorization code through a non-atomic find-then-delete sequence, allowing two concurrent requests to pass the read step and mint independent access tokens, refresh tokens, and ID tokens; legacy /oauth2/token and /mcp/token paths in oidc-provider and mcp plugins share the same primitive. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53518- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-362, CWE-367
- Updated
- 2026-11-02
EUVDEUVD-2026-44734
7.6
Better Auth is an authentication and authorization library for TypeScript. From 1.6.0 until 1.6.11, the @better-auth/oauth-provider POST /oauth2/token endpoint for the authorization_code grant redeems a single-use authorization code through a non-atomic find-then-delete sequence, allowing two concurrent requests to pass the read step and mint independent access tokens, refresh tokens, and ID tokens; legacy /oauth2/token and /mcp/token paths in oidc-provider and mcp plugins share the same primitive. This issue is fixed in version 1.6.11.
- Record
EUVD-2026-44734- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7w99-5wm4-3g79
7.6
@better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive
Am I affected?
Users are affected if all of the following are true:
- Their project depends on
@better-auth/oauth-providerat a version>= 1.6.0, < 1.6.11, or uses the embedded plugin inbetter-auth >= 1.4.8-beta.7, < 1.6.0, or enables the legacyoidc-providerormcpplugins frombetter-auth/plugins. - Their application exposes
/api/auth/oauth2/token(or the legacy plugins'/oauth2/tokenand/mcp/token) as a token endpoint to OAuth/OIDC clients, including internal MCP clients (Claude Desktop, custom MCP tool callers, AI agents). - Their application has not implemented an external mitigation: a load-balancer-level idempotency cache keyed by
code, a database trigger that rejects duplicate token issuance for the same authorization code, or a custom adapter override that performs an atomic compare-and-delete.
Fix:
- Upgrade to
@better-auth/[email protected]or later. If developers use the legacy plugin paths frombetter-auth/plugins, upgradebetter-authto1.6.11or later. - If developers cannot upgrade, see workarounds below.
Summary
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 not enforce that under concurrency.
Details
The same architectural primitive (find a single-use verification row, then delete it, then trust the row to authorize) is used in 20 other call sites across the codebase. The deletion primitive returns Promise<void>, discarding the row count surfaced by adapter.deleteMany, so no call site can detect "another caller already claimed this row". The fix lands at the primitive layer rather than at any individual call site.
The fix introduces a claimVerificationByIdentifier primitive at the internal-adapter layer that performs an atomic claim-and-return, replaces the find-then-delete pair at this call site, and migrates the highest-impact variant sites in the same release.
Patches
Fixed in @better-auth/[email protected] and [email protected] for the legacy oidc-provider and mcp plugin paths. All three token-exchange call sites now consume the verification row through internalAdapter.consumeVerificationValue, an atomic claim primitive that deletes the row and returns its prior value in one operation. The first request to arrive takes the row and mints tokens; concurrent racers observe an empty result and return invalid_grant.
Error-code consistency is also tightened on the @better-auth/oauth-provider token endpoint: the malformed-verification-value branches previously returned a project-specific invalid_verification code, which is not part of RFC 6749 §5.2's response error set. Both branches now return invalid_grant so spec-compliant clients can branch on the standard code without a special case.
Workarounds
None of these close the bug fully without a code patch. Upgrading is the only good path.
- Network-layer: deploy an authorization-server-aware reverse proxy (Envoy, NGINX with Lua, custom Cloudflare Worker) that holds an in-flight registry keyed by the
codeparameter and serializes concurrent requests for the same code. Fragile under multi-instance deployments unless the registry is shared (Redis-backed). - Database-layer: add a SQL or Mongo uniqueness constraint that prevents two
oauthAccessTokenrows from being created with the same upstream code reference. Adapter-specific and not always feasible since the schema does not currently store the source code. - Application-layer: wrap
deleteVerificationByIdentifierwith a custom hook that uses…
- Record
GHSA-7w99-5wm4-3g79- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-294, CWE-362, CWE-367
- Updated
- 2026-11-02
NVD
7.6
Better Auth is an authentication and authorization library for TypeScript. From 1.6.0 until 1.6.11, the @better-auth/oauth-provider POST /oauth2/token endpoint for the authorization_code grant redeems a single-use authorization code through a non-atomic find-then-delete sequence, allowing two concurrent requests to pass the read step and mint independent access tokens, refresh tokens, and ID tokens; legacy /oauth2/token and /mcp/token paths in oidc-provider and mcp plugins share the same primitive. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53518- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-362, CWE-367
- Updated
- 2026-11-02
OSV
7.6
Better Auth OAuth Provider: Race Condition in Authorization Code Exchange Enables Multi-Use Code Redemption
Better Auth is an authentication and authorization library for TypeScript. From 1.6.0 until 1.6.11, the @better-auth/oauth-provider POST /oauth2/token endpoint for the authorization_code grant redeems a single-use authorization code through a non-atomic find-then-delete sequence, allowing two concurrent requests to pass the read step and mint independent access tokens, refresh tokens, and ID tokens; legacy /oauth2/token and /mcp/token paths in oidc-provider and mcp plugins share the same primitive. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53518- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-362, CWE-367
- Updated
- 2026-11-02
OSVGHSA-7w99-5wm4-3g79
7.6
@better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive
Am I affected?
Users are affected if all of the following are true:
- Their project depends on
@better-auth/oauth-providerat a version>= 1.6.0, < 1.6.11, or uses the embedded plugin inbetter-auth >= 1.4.8-beta.7, < 1.6.0, or enables the legacyoidc-providerormcpplugins frombetter-auth/plugins. - Their application exposes
/api/auth/oauth2/token(or the legacy plugins'/oauth2/tokenand/mcp/token) as a token endpoint to OAuth/OIDC clients, including internal MCP clients (Claude Desktop, custom MCP tool callers, AI agents). - Their application has not implemented an external mitigation: a load-balancer-level idempotency cache keyed by
code, a database trigger that rejects duplicate token issuance for the same authorization code, or a custom adapter override that performs an atomic compare-and-delete.
Fix:
- Upgrade to
@better-auth/[email protected]or later. If developers use the legacy plugin paths frombetter-auth/plugins, upgradebetter-authto1.6.11or later. - If developers cannot upgrade, see workarounds below.
Summary
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 not enforce that under concurrency.
Details
The same architectural primitive (find a single-use verification row, then delete it, then trust the row to authorize) is used in 20 other call sites across the codebase. The deletion primitive returns Promise<void>, discarding the row count surfaced by adapter.deleteMany, so no call site can detect "another caller already claimed this row". The fix lands at the primitive layer rather than at any individual call site.
The fix introduces a claimVerificationByIdentifier primitive at the internal-adapter layer that performs an atomic claim-and-return, replaces the find-then-delete pair at this call site, and migrates the highest-impact variant sites in the same release.
Patches
Fixed in @better-auth/[email protected] and [email protected] for the legacy oidc-provider and mcp plugin paths. All three token-exchange call sites now consume the verification row through internalAdapter.consumeVerificationValue, an atomic claim primitive that deletes the row and returns its prior value in one operation. The first request to arrive takes the row and mints tokens; concurrent racers observe an empty result and return invalid_grant.
Error-code consistency is also tightened on the @better-auth/oauth-provider token endpoint: the malformed-verification-value branches previously returned a project-specific invalid_verification code, which is not part of RFC 6749 §5.2's response error set. Both branches now return invalid_grant so spec-compliant clients can branch on the standard code without a special case.
Workarounds
None of these close the bug fully without a code patch. Upgrading is the only good path.
- Network-layer: deploy an authorization-server-aware reverse proxy (Envoy, NGINX with Lua, custom Cloudflare Worker) that holds an in-flight registry keyed by the
codeparameter and serializes concurrent requests for the same code. Fragile under multi-instance deployments unless the registry is shared (Redis-backed). - Database-layer: add a SQL or Mongo uniqueness constraint that prevents two
oauthAccessTokenrows from being created with the same upstream code reference. Adapter-specific and not always feasible since the schema does not currently store the source code. - Application-layer: wrap
deleteVerificationByIdentifierwith a custom hook that uses…
- Record
GHSA-7w99-5wm4-3g79- CVSS 4.0
- 7.6 High
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N - Weakness
- CWE-294, CWE-362, CWE-367
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
@better-auth/oauth-providernpm |
1.6.0 up to 1.6.11 | 1.6.11 |
better-authnpm |
before 1.6.11 | 1.6.11 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-53518 is a high vulnerability (CVSS 7.6) in Better-auth, published July 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.41%, the 35th percentile. Fixed in @better-auth/oauth-provider 1.6.11.
References
github.com/better-auth/better-auth/security/advisories/GHSA-7w99-5wm4-3g79
github.com/better-auth/better-auth/commit/b4bc65a007784b2eb0efb459e5fa6fd8055d3ec9
github.com/better-auth/better-auth/releases/tag/v1.6.11
nvd.nist.gov/vuln/detail/CVE-2026-53518
github.com/advisories/GHSA-7w99-5wm4-3g79
github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53518.json
github.com/better-auth/better-auth
Left out here: the rest of 2 source descriptions. The JSON record has them.