CVE-2026-53512: Better Auth: OAuth refresh-token replay via missing client authentication on oidc-provider and mcp plugins

Published Jul 7, 2026
·
Updated

Am I affected?

Users are affected if all of the following are true:

- Their application uses better-auth and has enabled at least one of: oidcProvider() (imported from better-auth/plugins/oidc-provider), or mcp() (imported from better-auth/plugins/mcp). - Their application has at least one confidential OAuth client registered (any client with type: "web" | "native" | "user-agent-based" in the oauthApplication table, or any trustedClients entry without type: "public"). Public clients with PKCE are not affected. - Their application uses better-auth at 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:

1. Upgrade to better-auth@1.6.11 or later. 2. Migrate from the deprecated oidcProvider() to @better-auth/oauth-provider when feasible. The new package enforces client authentication on both grants by default. 3. 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 refreshtoken grant authenticates the request entirely on possession of the bound refreshToken row and a matching clientid. Neither plugin verifies the registered confidential client's clientsecret on the refresh path. An attacker who obtains any valid refreshtoken (via database read, log capture, browser-side XSS, or CORS-amplified script in the mcp case) and the public clientid 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' authorizationcode grant correctly enforces clientsecret (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 refreshtoken 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 authorizationcode 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 better-auth@1.6.11. The legacy oidcProvider and mcp token endpoints now require clientsecret on the refreshtoken grant for confidential clients, using the same constant-time comparison the authorizationcode 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-provider if your deployment can adopt the new plugin. It enforces clientsecret on both grants. - Force all clients to public + PKCE: set every client's type: "public" and require PKCE. The bug is unreachable when there is no clientsecret to verify. - Network-layer ingress restriction: limit /api/auth/oauth2/token and /api/auth/mcp/token to known client IPs at the load balancer. Practical for server-to-server flows, not for end-user-device clients. - Out-of-band refresh-token rotation: on any suspicion of leak, run db.deleteMany({ model: "oauthAccessToken", where: [{ field: "clientId", value: <id> }] }) to invalidate all refresh tokens for the affected client. - For the mcp endpoint specifically: drop the wildcard CORS at an upstream proxy and replace with a tight allowlist.

Impact

- Indefinite confidential-client impersonation: an attacker holding any valid refreshtoken and the public clientid can mint access tokens and rotated refresh tokens indefinitely, until the row is revoked. Rotation refreshes the expiration window each call. - Resource access at the user's authorized scope: every minted access token carries the original user's authorization scope, so the attacker reads or writes whatever the resource server grants for that scope.

Credit

Reported by @subhanUmer.

Resources

- CWE-306: Missing Authentication for Critical Function - CWE-287: Improper Authentication - CWE-345: Insufficient Verification of Data Authenticity - CWE-863: Incorrect Authorization - RFC 6749 §6: Refreshing an Access Token - OAuth 2.1 §4.3: Refresh Token

Other sources

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 refreshtoken grant authenticates only possession of the bound refreshToken row and matching clientid, without verifying the confidential client's clientsecret, allowing an attacker with a valid refreshtoken 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.

MITRE

Affected Software

2 affected componentsFixes available
npm/better-auth<1.6.11
1.6.11
better-auth Better Auth Node.js<1.6.11

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/better-auth to a version that resolves this vulnerability.

    Fixed in 1.6.11
  2. Upgrade

    Upgrade better-auth to a version that resolves this vulnerability.

    Fixed in 1.6.11
  3. Configuration

    After upgrading to better-auth@1.6.11, ensure the /api/auth/mcp/token endpoint does not set Access-Control-Allow-Origin: * unconditionally; it should remove the wildcard and narrow the CORS allowlist as described in the fix.

    better-auth mcp endpoint (/api/auth/mcp/token) Access-Control-Allow-Origin = narrowed allowlist (removed wildcard '*' )
  4. Configuration

    For the mcp endpoint specifically, drop the wildcard CORS at an upstream proxy and replace it with a tight allowlist.

    better-auth mcp endpoint (upstream proxy) CORS (mcp endpoint wildcard) = tight allowlist
  5. Compensating control

    Force all clients to be public + PKCE: set every client's type: "public" and require PKCE (mitigates by removing confidential-client secret enforcement needs from the refresh-token flow).

  6. Compensating control

    Network-layer ingress restriction: limit /api/auth/oauth2/token and /api/auth/mcp/token to known client IPs at the load balancer.

  7. Operational

    On any suspicion of refresh_token leak, invalidate the affected client's tokens by running: db.deleteMany({ model: "oauthAccessToken", where: [{ field: "clientId", value: <id> }] }) to invalidate all refresh tokens for that client (also ends the indefinite refresh/rotation chain until new tokens are minted).

Event History

Jul 7, 2026
Advisory Published
via GitHub·08:11 PM
Data Sourced
via GitHub·08:11 PM
DescriptionSeverityWeaknessAffected Software
Jul 15, 2026
CVE Published
via MITRE·05:18 PM
Data Sourced
via MITRE·05:18 PM
DescriptionWeakness
Data Sourced
via NVD·06:16 PM
RemedyDescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-53512?

The severity of CVE-2026-53512 is rated as critical with a score of 9.1.

2

What type of vulnerability is CVE-2026-53512?

CVE-2026-53512 is classified as an XSS (Cross-Site Scripting) vulnerability.

3

Am I affected by CVE-2026-53512?

You are affected by CVE-2026-53512 if your application uses `better-auth` with specific enabled plugins.

4

How do I fix CVE-2026-53512?

To fix CVE-2026-53512, you should update to the latest version of `better-auth` as recommended in the advisories.

5

What actions should I take if I am using an affected version of `better-auth` related to CVE-2026-53512?

If you are using an affected version, it is crucial to immediately update your dependency to a patched version to mitigate the risk.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203