GitHub Advisoryadvisory · npm
GHSA-9h47-pqcx-hjr4 Better Auth has insecure cryptographic defaults in oidcProvider: alg=none advertised and plain PKCE accepted by default
HIGH
Official page ↗
- Ecosystem
- npm
- Packages
- better-auth
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N
- Weaknesses
- CWE-1188, CWE-327, CWE-757
- Reviewed
- 2026-07-07
Affected packages
• npm better-auth: < 1.6.11 → fixed in 1.6.11
Description
Am I affected?
Users are affected if all of the following are true:
• Their application uses `better-auth` at a version below the patched release.
• Their application enables `oidcProvider()` from `better-auth/plugins/oidc-provider` or `mcp()` from `better-auth/plugins/mcp` (the mcp plugin delegates to `oidcProvider` and inherits both defaults).
• For the algorithm-negotiation impact: relying parties of the application's OIDC server use a JWT verification library that performs algorithm negotiation from the discovery document without pinning to a specific signing algorithm.
• For the PKCE impact: the authorization URL is exposed to any party other than the user agent and the application's OP.
If the application only uses `@better-auth/oauth-provider` (the canonical replacement) and have not enabled the legacy plugins, it is not affected. The new package's discovery document excludes `none` and its authorize schema rejects `plain` at parse time.
Fix:
1. Upgrade to `[email protected]` or later.
2. Migrate from the deprecated `oidcProvider` and `mcp` plugins to `@better-auth/oauth-provider` when feasible.
3. If developers cannot upgrade their applications, see workarounds below.
Summary
The legacy `oidcProvider` and `mcp` plugins exhibit two related defects in their OIDC discovery and authorize surfaces.
The discovery document advertises `"none"` in `id_token_signing_alg_values_supported` (and, for `mcp`, in `resource_signing_alg_values_supported` on the OAuth protected-resource metadata). Any relying party that performs algorithm negotiation from this metadata without pinning to a real signing algorithm may accept unsigned tokens.
PKCE `plain` is enabled by default. The runtime gate in the authorize handler accepts `code_challenge_method=plain` under this default, and a missing `code_challenge_method` parameter is silently downgraded to `"plain"` before the allowlist check. Discovery advertises `code_challenge_methods_supported: ["S256"]`, contradicting the runtime acceptance of `plain`. RFC 9700 §2.1.1 (OAuth 2.1) explicitly forbids `plain`.
Details
The metadata builders unconditionally inject `"none"` into the alg list. The runtime authorize gate is structured so a buggy client that strips the `code_challenge_method` parameter still enters the plain code path because the handler rewrites the missing value to `"plain"` before the allowlist check fires.
`@better-auth/oauth-provider` (the deprecation target for `oidcProvider`) is not affected by either defect. The metadata builder uses a `JWSAlgorithms` type union that structurally excludes `"none"`. The authorize schema is `code_challenge_method: z.literal("S256").optional()`, which rejects `plain` at parse time.
Patches
Fixed in `[email protected]`. The legacy `oidcProvider` and `mcp` plugins now:
• Drop `"none"` from `id_token_signing_alg_values_supported` (both plugins) and from `resource_signing_alg_values_supported` (`mcp`). Discovery no longer advertises the unsigned-token option.
• Default `allowPlainCodeChallengeMethod` to `false`. A request that explicitly passes `code_challenge_method=plain` is rejected with `invalid_request` unless the integrator opts in.
• Reject a `code_challenge` without an accompanying `code_challenge_method` instead of silently rewriting the missing value to `plain`. Clients that send `code_challenge` must also send `code_challenge_method=S256`.
Discovery and runtime behavior align on `S256` only by default.
Integrators who must keep plain PKCE for legacy clients can restore the previous shape with `oidcProvider({ allowPlainCodeChallengeMethod: true })` (and likewise for `mcp`). With the opt-in set, a request that omits `code_challenge_method` is treated as `plain` again, preserving backwards compatibility while keeping the secure default for everyone else. Both legacy plugins are deprecated long-term; the recommended migration is `@better-auth/oauth-provider`, which never advertised `none` or accepted plain PKCE.
Workarounds
If developers cannot upgrade their applications immediately:
• Disable plain PKCE explicitly: set `oidcProvider({ allowPlainCodeChallengeMethod: false })` (and the equivalent on `mcp`). Closes the runtime acceptance of `plain` even though the silent downgrade still rewrites missing methods.
• Override the metadata to drop `"none"` from `id_token_signing_alg_values_supported`. For `oidcProvider`, pass `metadata: { id_token_signing_alg_values_supported: ["RS256"] }`. For `mcp`, set the same on `options.oidcConfig.metadata`. Verify by curling the `.well-known` endpoint.
• Migrate to `@better-auth/oauth-provider`: the package is the deprecation target and is unaffected by both defects.
Impact
• Algorithm-negotiation downgrade: relying parties that read the discovery document without pinning may accept unsigned (`alg: "none"`) tokens.
• Authorization-code interception: PKCE `plain` does not protect the authorization code if the URL leaks (Referer headers, browser history, screen capture, proxy logs). PKCE `S256` is what protects against that exposure; with `plain` the protection is absent.
• OAuth 2.1 / RFC 9700 non-conformance: deployments shipping the defaults are non-compliant with current standards.
Credit
Reported by @subhanUmer.
Resources
• CWE-327: Use of a Broken or Risky Cryptographic Algorithm
• CWE-757: Selection of Less-Secure Algorithm During Negotiation
• CWE-1188: Insecure Default Initialization of Resource
• RFC 9700 §2.1.1: PKCE
• RFC 9700 §2.1.2: Token Replay Prevention
• RFC 8414 §2: Authorization Server Metadata