NewApp Monitoring is liveSign up free
AgentbeamBeta
AI agent vulnerability · Other MCP servers and tools

CVE-2026-53518: @better-auth/oauth-provider's OAuth authorization-code grant allows concurrent redemption when two token requests race the find-then-delete primitive

highCVSS 4.0 7.6 · 3.1 8.1GHSA-7w99-5wm4-3g79

Better Auth's OAuth provider redeems a single-use authorization code with a non-atomic find-then-delete. Two token requests sent at the same moment with the same code both succeed, and each gets its own access, refresh and id tokens. Affects @better-auth/oauth-provider 1.6.0 to 1.6.10, the embedded plugin and the legacy oidc-provider and mcp plugins. Fixed in 1.6.11.

Published Jul 7, 2026 · Updated Jul 20, 2026 · Analysis Oct 25, 2026

What an attacker can do

On the authorization_code grant, POST /oauth2/token reads the verification row for the code, then deletes it. Two concurrent requests both pass the read before either delete lands. Both then check PKCE and call createUserTokens. RFC 6749 section 4.1.2 requires codes to be single use, and this race breaks that rule.

The result is several independent token sets from one user authorization, all valid for that user's scope. The single-use check that would normally flag a replayed code does not fire when the requests interleave. An attacker needs a captured code. The CVSS vector marks user interaction (the user must authorize) and no privileges. The practical case is a stolen or intercepted code that the legitimate client redeems at the same time as the attacker, leaving the user with no sign that a second token set exists.

The legacy oidc-provider and mcp plugins share the same primitive. That matters for apps that expose a token endpoint to MCP clients such as Claude Desktop, custom MCP callers or AI agents. The maintainers note 20 other call sites use the same find-then-delete pattern.

Who is affected

Affected: @better-auth/oauth-provider from 1.6.0 up to but not including 1.6.11, and the embedded plugin in better-auth from 1.4.8-beta.7 before 1.6.0. The legacy oidc-provider and mcp plugins from better-auth/plugins are affected on better-auth below 1.6.11. You are exposed if you serve /api/auth/oauth2/token, /oauth2/token or /mcp/token and have no external mitigation such as an idempotency cache keyed by code.

npm ls @better-auth/oauth-provider better-auth

@better-auth/oauth-provider

Ecosystem
npm
Affected
>= 1.6.0, < 1.6.11
Fixed in
1.6.11
Check
npm ls @better-auth/oauth-provider

better-auth

Ecosystem
npm
Affected
< 1.6.11
Fixed in
1.6.11
Check
npm ls better-auth

How to fix it

Upgrade @better-auth/oauth-provider to 1.6.11 or later. If you use the legacy plugin paths, upgrade better-auth to 1.6.11 or later.

npm install @better-auth/[email protected] [email protected]

The patch routes all three token-exchange call sites through internalAdapter.consumeVerificationValue, which deletes the row and returns its value in one operation. The first request wins. Racing requests get invalid_grant. The release also adds a unique constraint on oauthRefreshToken.token in a related fix, so check whether you need to run your database migration.

The advisory says no workaround fully closes the bug. Partial options are a reverse proxy that serializes requests per code, a database uniqueness rule, or a wrapper that rejects when deleteMany removes zero rows.

How to tell if you were hit

Look in token-endpoint logs and in the token table for two issuances tied to the same authorization code, with timestamps within milliseconds of each other. Two refresh tokens for one grant that you cannot explain is a sign. The patched endpoint returns invalid_grant for the losing request, so those errors in logs after the upgrade show attempted races. If you cannot upgrade, revoke tokens issued from suspect grants and ask the user to re-authorize.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

Am I affected?

Users are affected if all of the following are true:

  • Their project depends on @better-auth/oauth-provider at a version >= 1.6.0, < 1.6.11, or uses the embedded plugin in better-auth >= 1.4.8-beta.7, < 1.6.0, or enables the legacy oidc-provider or mcp plugins from better-auth/plugins.
  • Their application exposes /api/auth/oauth2/token (or the legacy plugins' /oauth2/token and /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:

  1. Upgrade to @better-auth/[email protected] or later. If developers use the legacy plugin paths from better-auth/plugins, upgrade better-auth to 1.6.11 or later.
  2. 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 code parameter 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 oauthAccessToken rows 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 deleteVerificationByIdentifier with a custom hook that uses adapter.deleteMany and surfaces the count, then injects an invalid_grant rejection when the count is zero. Requires forking the internal adapter.

Impact

  • Multiple independent token sets from a single authorization: forked access tokens, refresh tokens, and id tokens issued from the same code, all valid for the original user's authorization scope.
  • Detection bypass: standard OAuth single-use enforcement does not fire for the second redemption when both requests interleave through the read step.
  • Legacy-plugin reach: oidc-provider and mcp plugins share the primitive on the same surface, so deployments using them inherit the same impact.

Credit

Reported by @chdanielmueller.

Resources

Credited to chdanielmueller.

Questions

Does CVE-2026-53518 affect MCP servers?

Yes, if they use Better Auth's mcp plugin or the OAuth provider. The advisory names the /mcp/token endpoint and lists internal MCP clients and AI agents among the callers that can be exposed.

Is there a workaround if I can't upgrade?

Only partial ones. The advisory lists a proxy that serializes requests by code, a database uniqueness constraint, or an adapter wrapper that rejects when no row was deleted. It says upgrading is the only good path.

What version fixes it?

@better-auth/oauth-provider 1.6.11, and better-auth 1.6.11 for the legacy oidc-provider and mcp plugins.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More Other MCP servers and tools vulnerabilities

All Other MCP servers and tools entries