CVE-2026-53512
Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, the legacy oidcProvider and mcp plugins expose OAuth token endpoints whose refresh_token grant authenticates only possession of the bound refreshToken row and matching client_id, without verifying the confidential client's client_secret, allowing an attacker with a valid refresh_token to mint access tokens and rotated refresh tokens through /api/auth/oauth2/token or /api/auth/mcp/token. The @better-auth/oauth-provider package is not affected. 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
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
9.1
Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, the legacy oidcProvider and mcp plugins expose OAuth token endpoints whose refresh_token grant authenticates only possession of the bound refreshToken row and matching client_id, without verifying the confidential client's client_secret, allowing an attacker with a valid refresh_token to mint access tokens and rotated refresh tokens through /api/auth/oauth2/token or /api/auth/mcp/token. The @better-auth/oauth-provider package is not affected. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53512- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-287, CWE-306, CWE-345, CWE-863
- Updated
- 2026-11-02
EUVDEUVD-2026-44735
9.1
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, the legacy oidcProvider and mcp plugins expose OAuth token endpoints whose refresh_token grant authenticates only possession of the bound refreshToken row and matching client_id, without verifying the confidential client's client_secret, allowing an attacker with a valid refresh_token to mint access tokens and rotated refresh tokens through /api/auth/oauth2/token or /api/auth/mcp/token. The @better-auth/oauth-provider package is not affected. This issue is fixed in version 1.6.11.
- Record
EUVD-2026-44735- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-pw9m-5jxm-xr6h
9.1
Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Am I affected?
Users are affected if all of the following are true:
- Their application uses
better-authand has enabled at least one of:oidcProvider()(imported frombetter-auth/plugins/oidc-provider), ormcp()(imported frombetter-auth/plugins/mcp). - Their application has at least one confidential OAuth client registered (any client with
type: "web" | "native" | "user-agent-based"in theoauthApplicationtable, or anytrustedClientsentry withouttype: "public"). Public clients with PKCE are not affected. - Their application uses
better-authat a version below the patched release.
If an application only uses @better-auth/oauth-provider (the canonical replacement for oidc-provider) and the mcp plugin is not enabled, it is not affected.
Fix:
- Upgrade to
[email protected]or later. - Migrate from the deprecated
oidcProvider()to@better-auth/oauth-providerwhen feasible. The new package enforces client authentication on both grants by default. - If developers cannot upgrade their applications, see workarounds below.
Summary
The legacy oidcProvider and mcp plugins each expose an OAuth 2.0 token endpoint whose refresh_token grant authenticates the request entirely on possession of the bound refreshToken row and a matching client_id. Neither plugin verifies the registered confidential client's client_secret on the refresh path. An attacker who obtains any valid refresh_token (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public client_id can mint fresh access tokens and rotated refresh tokens until the chain is revoked.
Details
RFC 6749 §6 and OAuth 2.1 §4.3 require confidential clients to authenticate to the token endpoint on every grant, including refresh. The same plugins' authorization_code grant correctly enforces client_secret (the oidc-provider via verifyStoredClientSecret, the mcp plugin via raw equality), which proves the omission on the refresh path is a regression rather than a design choice.
Token rotation issues a new refresh_token with each call, so a single leaked refresh-token grants indefinite access until the row is revoked or its refreshTokenExpiresAt (default 7 days) passes; rotation refreshes that window each call.
Two adjacent issues on the mcp surface ship in the same patch. The mcp authorization_code grant uses raw === for client-secret comparison and ignores the storeClientSecret: "encrypted" | "hashed" configuration; the fix routes both grants through verifyStoredClientSecret. The mcp /mcp/token endpoint sets Access-Control-Allow-Origin: * unconditionally, which amplifies the refresh bypass in browser contexts; the fix narrows the CORS allowlist.
The newer @better-auth/oauth-provider package routes both grants through validateClientCredentials and is not affected.
Patches
Fixed in [email protected]. The legacy oidcProvider and mcp token endpoints now require client_secret on the refresh_token grant for confidential clients, using the same constant-time comparison the authorization_code grant already used. Public clients are unaffected (they have no secret to enforce, and PKCE substitutes on the auth-code grant).
The Authorization: Basic parser is fixed to follow RFC 6749 §2.3.1: the credential is split on the first colon and each half is percent-decoded. Client IDs and secrets that contain reserved characters now authenticate correctly. The /mcp/token endpoint's CORS configuration is narrowed in the same change (the wildcard Access-Control-Allow-Origin: * header is removed), matching the standalone @better-auth/oauth-provider package.
The deprecated oidc-provider plugin remains deprecated. The recommended migration path is @better-auth/oauth-provider.
Workarounds
None of these close the bug fully without a code patch.
- Migrate to
@better-auth/oauth-providerif your deployment can adopt the new…
- Record
GHSA-pw9m-5jxm-xr6h- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/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
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-287, CWE-306, CWE-345, CWE-863
- Updated
- 2026-11-02
NVD
9.1
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, the legacy oidcProvider and mcp plugins expose OAuth token endpoints whose refresh_token grant authenticates only possession of the bound refreshToken row and matching client_id, without verifying the confidential client's client_secret, allowing an attacker with a valid refresh_token to mint access tokens and rotated refresh tokens through /api/auth/oauth2/token or /api/auth/mcp/token. The @better-auth/oauth-provider package is not affected. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53512- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/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
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-287, CWE-306, CWE-345, CWE-863
- Updated
- 2026-11-02
OSV
9.1
Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Better Auth is an authentication and authorization library for TypeScript. Prior to 1.6.11, the legacy oidcProvider and mcp plugins expose OAuth token endpoints whose refresh_token grant authenticates only possession of the bound refreshToken row and matching client_id, without verifying the confidential client's client_secret, allowing an attacker with a valid refresh_token to mint access tokens and rotated refresh tokens through /api/auth/oauth2/token or /api/auth/mcp/token. The @better-auth/oauth-provider package is not affected. This issue is fixed in version 1.6.11.
- Record
CVE-2026-53512- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-287, CWE-306, CWE-345, CWE-863
- Updated
- 2026-11-02
OSVGHSA-pw9m-5jxm-xr6h
9.1
Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins
Am I affected?
Users are affected if all of the following are true:
- Their application uses
better-authand has enabled at least one of:oidcProvider()(imported frombetter-auth/plugins/oidc-provider), ormcp()(imported frombetter-auth/plugins/mcp). - Their application has at least one confidential OAuth client registered (any client with
type: "web" | "native" | "user-agent-based"in theoauthApplicationtable, or anytrustedClientsentry withouttype: "public"). Public clients with PKCE are not affected. - Their application uses
better-authat a version below the patched release.
If an application only uses @better-auth/oauth-provider (the canonical replacement for oidc-provider) and the mcp plugin is not enabled, it is not affected.
Fix:
- Upgrade to
[email protected]or later. - Migrate from the deprecated
oidcProvider()to@better-auth/oauth-providerwhen feasible. The new package enforces client authentication on both grants by default. - If developers cannot upgrade their applications, see workarounds below.
Summary
The legacy oidcProvider and mcp plugins each expose an OAuth 2.0 token endpoint whose refresh_token grant authenticates the request entirely on possession of the bound refreshToken row and a matching client_id. Neither plugin verifies the registered confidential client's client_secret on the refresh path. An attacker who obtains any valid refresh_token (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public client_id can mint fresh access tokens and rotated refresh tokens until the chain is revoked.
Details
RFC 6749 §6 and OAuth 2.1 §4.3 require confidential clients to authenticate to the token endpoint on every grant, including refresh. The same plugins' authorization_code grant correctly enforces client_secret (the oidc-provider via verifyStoredClientSecret, the mcp plugin via raw equality), which proves the omission on the refresh path is a regression rather than a design choice.
Token rotation issues a new refresh_token with each call, so a single leaked refresh-token grants indefinite access until the row is revoked or its refreshTokenExpiresAt (default 7 days) passes; rotation refreshes that window each call.
Two adjacent issues on the mcp surface ship in the same patch. The mcp authorization_code grant uses raw === for client-secret comparison and ignores the storeClientSecret: "encrypted" | "hashed" configuration; the fix routes both grants through verifyStoredClientSecret. The mcp /mcp/token endpoint sets Access-Control-Allow-Origin: * unconditionally, which amplifies the refresh bypass in browser contexts; the fix narrows the CORS allowlist.
The newer @better-auth/oauth-provider package routes both grants through validateClientCredentials and is not affected.
Patches
Fixed in [email protected]. The legacy oidcProvider and mcp token endpoints now require client_secret on the refresh_token grant for confidential clients, using the same constant-time comparison the authorization_code grant already used. Public clients are unaffected (they have no secret to enforce, and PKCE substitutes on the auth-code grant).
The Authorization: Basic parser is fixed to follow RFC 6749 §2.3.1: the credential is split on the first colon and each half is percent-decoded. Client IDs and secrets that contain reserved characters now authenticate correctly. The /mcp/token endpoint's CORS configuration is narrowed in the same change (the wildcard Access-Control-Allow-Origin: * header is removed), matching the standalone @better-auth/oauth-provider package.
The deprecated oidc-provider plugin remains deprecated. The recommended migration path is @better-auth/oauth-provider.
Workarounds
None of these close the bug fully without a code patch.
- Migrate to
@better-auth/oauth-providerif your deployment can adopt the new…
- Record
GHSA-pw9m-5jxm-xr6h- CVSS 4.0
- 9.1 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/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
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-287, CWE-306, CWE-345, CWE-863
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
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-53512 is a critical vulnerability (CVSS 9.1) 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.27%, the 20th percentile. Fixed in better-auth 1.6.11.
References
github.com/better-auth/better-auth/security/advisories/GHSA-pw9m-5jxm-xr6h
github.com/better-auth/better-auth/pull/9576
github.com/better-auth/better-auth/commit/1f2ff4215c4affff0b140b0c0a712c0dde35659c
github.com/better-auth/better-auth/releases/tag/v1.6.11
nvd.nist.gov/vuln/detail/CVE-2026-53512
github.com/advisories/GHSA-pw9m-5jxm-xr6h
github.com/CVEProject/cvelistV5/tree/main/cves/2026/53xxx/CVE-2026-53512.json
github.com/better-auth/better-auth
Left out here: the rest of 2 source descriptions. The JSON record has them.