[GH-ISSUE #8016] oauth-provider: /oauth2/token crashes on clientSecret format mismatch (hex decode error) instead of invalid_client #28294

Closed
opened 2026-04-17 19:43:45 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @ikemHood on GitHub (Feb 17, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8016

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create an Express backend using better-auth@1.4.18 + @better-auth/oauth-provider@1.4.18.
  2. Configure OAuth provider and use persisted OAuth clients (oauthClient.clientSecret) created before the latest config/runtime changes.
  3. Run OAuth authorization code flow from a client app using generic OAuth.
  4. POST to /oauth2/token with valid form-urlencoded payload:
    • grant_type=authorization_code
    • code
    • code_verifier
    • redirect_uri
    • client_id
    • client_secret
  5. Observe server crash during token exchange with:
    • hex string expected, got unpadded hex of length 43
    • stack includes decryptStoredClientSecret / verifyStoredClientSecret / validateClientCredentials.

Current vs. Expected behavior

Current behavior:

  • Token request reaches /oauth2/token correctly (form-urlencoded body includes grant_type and other expected fields), but client secret verification throws:
    hex string expected, got unpadded hex of length 43
  • This appears to happen when stored oauthClient.clientSecret format does not match decrypt expectations.
  • Endpoint returns server error instead of a controlled OAuth error.

Expected behavior:

  • If stored secret format is incompatible, return a controlled OAuth error (for example invalid_client) instead of throwing an internal crypto parsing error.
  • Ideally provide clearer compatibility/migration handling between hashed and encrypted client-secret storage formats.

What version of Better Auth are you using?

1.4.18

System info

OS: macOS 15.1.1 (24B91), Darwin 24.1.0, arm64
Node.js: v22.11.0
npm: 11.4.2
pnpm: 10.17.0
better-auth: 1.4.18
@better-auth/oauth-provider: 1.4.18

Which area(s) are affected? (Select all that apply)

  • Backend
  • Package

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { oauthProvider } from "@better-auth/oauth-provider";

export const auth = betterAuth({
  // adapter + base config omitted
  plugins: [
    oauthProvider({
      loginPage: "/oauth/login",
      consentPage: "/oauth/consent",
      accessTokenExpiresIn: 60 * 60,
      refreshTokenExpiresIn: 60 * 60 * 24 * 30,
      codeExpiresIn: 60 * 10,
      scopes: ["openid", "profile", "email", "offline_access"],
      // Our compatibility workaround:
      // storeClientSecret: "hashed",
    }),
  ],
});

Additional context

  • We originally had a separate body-parsing issue where /oauth2/token got an empty body. After fixing middleware order/scoping, request body is now confirmed correct.
  • Example observed token-request diagnostics after fix:
    • contentType: application/x-www-form-urlencoded
    • bodyHasGrantType: true
    • bodyKeys: grant_type, code, code_verifier, redirect_uri, client_id, client_secret
  • The remaining bug is specifically in client secret verification/decryption path.
  • Temporary workaround on our side: explicitly pin storeClientSecret: "hashed" for compatibility with existing clients.
Originally created by @ikemHood on GitHub (Feb 17, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8016 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create an Express backend using `better-auth@1.4.18` + `@better-auth/oauth-provider@1.4.18`. 2. Configure OAuth provider and use persisted OAuth clients (`oauthClient.clientSecret`) created before the latest config/runtime changes. 3. Run OAuth authorization code flow from a client app using generic OAuth. 4. POST to `/oauth2/token` with valid form-urlencoded payload: - `grant_type=authorization_code` - `code` - `code_verifier` - `redirect_uri` - `client_id` - `client_secret` 5. Observe server crash during token exchange with: - `hex string expected, got unpadded hex of length 43` - stack includes `decryptStoredClientSecret` / `verifyStoredClientSecret` / `validateClientCredentials`. ### Current vs. Expected behavior Current behavior: - Token request reaches `/oauth2/token` correctly (form-urlencoded body includes `grant_type` and other expected fields), but client secret verification throws: `hex string expected, got unpadded hex of length 43` - This appears to happen when stored `oauthClient.clientSecret` format does not match decrypt expectations. - Endpoint returns server error instead of a controlled OAuth error. Expected behavior: - If stored secret format is incompatible, return a controlled OAuth error (for example `invalid_client`) instead of throwing an internal crypto parsing error. - Ideally provide clearer compatibility/migration handling between hashed and encrypted client-secret storage formats. ### What version of Better Auth are you using? 1.4.18 ### System info ```bash OS: macOS 15.1.1 (24B91), Darwin 24.1.0, arm64 Node.js: v22.11.0 npm: 11.4.2 pnpm: 10.17.0 better-auth: 1.4.18 @better-auth/oauth-provider: 1.4.18 ``` ### Which area(s) are affected? (Select all that apply) - Backend - Package ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { oauthProvider } from "@better-auth/oauth-provider"; export const auth = betterAuth({ // adapter + base config omitted plugins: [ oauthProvider({ loginPage: "/oauth/login", consentPage: "/oauth/consent", accessTokenExpiresIn: 60 * 60, refreshTokenExpiresIn: 60 * 60 * 24 * 30, codeExpiresIn: 60 * 10, scopes: ["openid", "profile", "email", "offline_access"], // Our compatibility workaround: // storeClientSecret: "hashed", }), ], }); ``` ### Additional context - We originally had a separate body-parsing issue where `/oauth2/token` got an empty body. After fixing middleware order/scoping, request body is now confirmed correct. - Example observed token-request diagnostics after fix: - `contentType: application/x-www-form-urlencoded` - `bodyHasGrantType: true` - `bodyKeys: grant_type, code, code_verifier, redirect_uri, client_id, client_secret` - The remaining bug is specifically in client secret verification/decryption path. - Temporary workaround on our side: explicitly pin `storeClientSecret: "hashed"` for compatibility with existing clients.
GiteaMirror added the lockedbug labels 2026-04-17 19:43:45 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28294