[GH-ISSUE #6651] [OIDC Provider]: Public Clients Require Non-Empty clientSecret Despite Not Needing It #27908

Closed
opened 2026-04-17 19:11:40 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @inducingchaos on GitHub (Dec 10, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6651

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure BetterAuth with OIDC provider plugin
  2. Add a trusted client with type: "public" (for PKCE flow)
  3. Set clientSecret: undefined or omit it entirely
  4. Attempt to use OAuth endpoints (e.g., token exchange)
  5. Observe errors related to empty/null client secret values

Minimal reproduction:

// auth.ts
import { betterAuth } from "better-auth"
import { oidcProvider } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        oidcProvider({
            trustedClients: [
                {
                    clientId: "my-native-app",
                    name: "My Native App",
                    type: "public", // PKCE client - should not need secret
                    // clientSecret: undefined, // or omitted
                    redirectUrls: ["myapp://callback"],
                    disabled: false
                }
            ]
        })
    ]
})

Error observed:

Error [DataError]: Zero-length key is not supported

This occurs when BetterAuth tries to use the client secret for operations (like signing ID tokens) even though public clients don't need secrets.

Current vs. Expected behavior

Current behavior:
Public clients (type: "public") require a non-empty clientSecret value even though:

  • Public clients use PKCE and don't authenticate with client secrets
  • The OAuth 2.0 spec explicitly states public clients don't need secrets
  • Setting clientSecret: undefined or omitting it causes errors in various endpoints

Workaround:
Users must set a placeholder secret:

{
  clientId: "my-app",
  type: "public",
  clientSecret: "placeholder-secret", // Required but not actually used
  // ...
}

Expected behavior:
Public clients should:

  • Accept clientSecret: undefined or allow omitting the field entirely
  • Skip client secret validation/usage for public clients
  • Only use client secrets for confidential clients (type: "web" or similar)

Impact:

  • Confusing API: users must provide a "fake" secret for public clients
  • Potential security confusion: users might think they need to protect a secret that's not actually used
  • Inconsistent with OAuth 2.0 spec which states public clients don't need secrets

What version of Better Auth are you using?

1.4.5

System info

{                                                       "system": {                                             "platform": "darwin",                                 "arch": "arm64",                                      "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:38:03 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T8112",                                              "release": "25.0.0",                                  "cpuCount": 8,                                        "cpuModel": "Apple M2",                               "totalMemory": "8.00 GB",                             "freeMemory": "0.16 GB"
  },                                                    "node": {                                               "version": "v22.20.0",                                "env": "development"                                },
  "packageManager": {                                     "name": "pnpm",                                       "version": "10.20.0"                                },
  "frameworks": [                                         {                                                       "name": "next",                                       "version": "16.0.8"                                 },                                                    {                                                       "name": "react",                                      "version": "catalog:react"                          }                                                   ],                                                    "databases": [                                          {                                                       "name": "postgres",                                   "version": "^3.4.7"                                 },                                                    {                                                       "name": "drizzle",                                    "version": "catalog:drizzle"                        }                                                   ],                                                    "betterAuth": {                                         "version": "catalog:better-auth",                     "config": {                                             "appName": "ALTERED",                                 "baseURL": "http://localhost:258",                    "secret": "[REDACTED]",                               "socialProviders": {                                    "google": {                                             "clientId": "[REDACTED]",                             "clientSecret": "[REDACTED]"                        }                                                   },                                                    "plugins": [                                            {                                                       "name": "next-cookies",                               "config": {                                             "id": "next-cookies",                                 "hooks": {                                              "after": [                                              {}                                                  ]                                                   }                                                   }                                                   },                                                    {                                                       "name": "oidc",                                       "config": {                                             "id": "oidc",                                         "hooks": {                                              "after": [                                              {}                                                  ]                                                   },                                                    "endpoints": {},                                      "schema": {                                             "oauthApplication": {                                   "modelName": "oauthApplication",                      "fields": {                                             "name": {                                               "type": "string"                                    },                                                    "icon": {                                               "type": "string",                                     "required": false                                   },                                                    "metadata": {                                           "type": "string",                                     "required": false                                   },                                                    "clientId": {
                    "type": "string",                                     "unique": true                                      },                                                    "clientSecret": {
                    "type": "string",
                    "required": false                                   },                                                    "redirectUrls": {                                       "type": "string"                                    },                                                    "type": {                                               "type": "string"                                    },                                                    "disabled": {
                    "type": "boolean",                                    "required": false,                                    "defaultValue": false                               },                                                    "userId": {                                             "type": "string",                                     "required": false,                                    "references": {                                         "model": "user",                                      "field": "id",                                        "onDelete": "cascade"                               },                                                    "index": true                                       },                                                    "createdAt": {                                          "type": "date"                                      },                                                    "updatedAt": {                                          "type": "date"                                      }                                                   }                                                   },                                                    "oauthAccessToken": {                                   "modelName": "oauthAccessToken",                      "fields": {
                  "accessToken": {                                        "type": "string",
                    "unique": true                                      },                                                    "refreshToken": {
                    "type": "string",                                     "unique": true                                      },                                                    "accessTokenExpiresAt": {                               "type": "date"                                      },                                                    "refreshTokenExpiresAt": {                              "type": "date"                                      },                                                    "clientId": {                                           "type": "string",                                     "references": {                                         "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"                               },
                    "index": true
                  },
                  "userId": {                                             "type": "string",
                    "required": false,                                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {                                             "type": "string"
                  },
                  "createdAt": {
                    "type": "date"                                      },
                  "updatedAt": {                                          "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {                                             "clientId": {
                    "type": "string",                                     "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true                                       },
                  "userId": {                                             "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },                                                    "index": true
                  },                                                    "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },                                                    "updatedAt": {
                    "type": "date"                                      },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }                                                   },
            "options": {                                            "codeExpiresIn": 600,
              "defaultScope": "openid",
              "accessTokenExpiresIn": 3600,
              "refreshTokenExpiresIn": 604800,                      "allowPlainCodeChallengeMethod": true,
              "storeClientSecret": "[REDACTED]",                    "loginPage": "/sign-in",
              "trustedClients": [
                {
                  "clientId": "[REDACTED]",
                  "name": "ALTERED for Raycast",
                  "type": "public",
                  "clientSecret": "[REDACTED]",
                  "icon": "icon.png",                                   "redirectUrls": [
                    "https://raycast.com/redirect?packageName=Extension"
                  ],
                  "disabled": false,
                  "skipConsent": true,                                  "metadata": {
                    "platform": "raycast"
                  }                                                   }
              ],                                                    "scopes": [
                "openid",                                             "profile",
                "email",
                "offline_access"
              ]
            }
          }
        },
        {
          "name": "oidc-polyfill",                              "config": {
            "id": "oidc-polyfill",                                "endpoints": {}
          }
        }
      ],                                                    "advanced": {                                           "database": {}                                      }
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { oidcProvider } from "better-auth/plugins"
import { db } from "~/server/data/store"

export const auth = betterAuth({
    database: drizzleAdapter(db, {
        provider: "pg",
        usePlural: true
    }),
    plugins: [
        oidcProvider({
            loginPage: "/sign-in",
            trustedClients: [
                {
                    clientId: "altered-launcher",
                    name: "ALTERED for Raycast",
                    type: "public",
                    clientSecret: "placeholder-secret",
                    redirectUrls: ["https://raycast.com/redirect?packageName=Extension"],
                    disabled: false,
                    skipConsent: true,
                    metadata: { platform: "raycast" }
                }
            ]
        })
    ]
})

Additional context

OAuth 2.0 Specification:
According to RFC 6749 (OAuth 2.0), public clients are clients that cannot maintain the confidentiality of their credentials. They use PKCE (RFC 7636) for security instead of client secrets. Public clients should not require client secrets.

Where the issue manifests:

  1. ID Token signing (line ~800+ in token endpoint): When useJWTPlugin: false, the code uses client.clientSecret to sign ID tokens with HS256, even for public clients
  2. Client secret validation: Some validation paths check for non-empty secrets even for public clients
  3. Type definitions: The TypeScript types may not properly reflect that clientSecret is optional for public clients

Suggested fix:

  1. Make clientSecret optional in types for public clients
  2. Skip client secret operations for public clients (use different signing method or skip signing)
  3. Add proper type guards to check client type before using secrets
  4. For ID token signing with public clients, either:
    • Require JWT plugin (asymmetric keys)
    • Use a different signing mechanism
    • Skip signing if no secret is available

Reference:

Originally created by @inducingchaos on GitHub (Dec 10, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6651 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure BetterAuth with OIDC provider plugin 2. Add a trusted client with `type: "public"` (for PKCE flow) 3. Set `clientSecret: undefined` or omit it entirely 4. Attempt to use OAuth endpoints (e.g., token exchange) 5. Observe errors related to empty/null client secret values **Minimal reproduction:** ```typescript // auth.ts import { betterAuth } from "better-auth" import { oidcProvider } from "better-auth/plugins" export const auth = betterAuth({ plugins: [ oidcProvider({ trustedClients: [ { clientId: "my-native-app", name: "My Native App", type: "public", // PKCE client - should not need secret // clientSecret: undefined, // or omitted redirectUrls: ["myapp://callback"], disabled: false } ] }) ] }) ``` **Error observed:** ``` Error [DataError]: Zero-length key is not supported ``` This occurs when BetterAuth tries to use the client secret for operations (like signing ID tokens) even though public clients don't need secrets. ### Current vs. Expected behavior **Current behavior:** Public clients (`type: "public"`) require a non-empty `clientSecret` value even though: - Public clients use PKCE and don't authenticate with client secrets - The OAuth 2.0 spec explicitly states public clients don't need secrets - Setting `clientSecret: undefined` or omitting it causes errors in various endpoints **Workaround:** Users must set a placeholder secret: ```typescript { clientId: "my-app", type: "public", clientSecret: "placeholder-secret", // Required but not actually used // ... } ``` **Expected behavior:** Public clients should: - Accept `clientSecret: undefined` or allow omitting the field entirely - Skip client secret validation/usage for public clients - Only use client secrets for confidential clients (`type: "web"` or similar) **Impact:** - Confusing API: users must provide a "fake" secret for public clients - Potential security confusion: users might think they need to protect a secret that's not actually used - Inconsistent with OAuth 2.0 spec which states public clients don't need secrets ### What version of Better Auth are you using? 1.4.5 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:38:03 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T8112", "release": "25.0.0", "cpuCount": 8, "cpuModel": "Apple M2", "totalMemory": "8.00 GB", "freeMemory": "0.16 GB" }, "node": { "version": "v22.20.0", "env": "development" }, "packageManager": { "name": "pnpm", "version": "10.20.0" }, "frameworks": [ { "name": "next", "version": "16.0.8" }, { "name": "react", "version": "catalog:react" } ], "databases": [ { "name": "postgres", "version": "^3.4.7" }, { "name": "drizzle", "version": "catalog:drizzle" } ], "betterAuth": { "version": "catalog:better-auth", "config": { "appName": "ALTERED", "baseURL": "http://localhost:258", "secret": "[REDACTED]", "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "plugins": [ { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "after": [ {} ] } } }, { "name": "oidc", "config": { "id": "oidc", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } }, "options": { "codeExpiresIn": 600, "defaultScope": "openid", "accessTokenExpiresIn": 3600, "refreshTokenExpiresIn": 604800, "allowPlainCodeChallengeMethod": true, "storeClientSecret": "[REDACTED]", "loginPage": "/sign-in", "trustedClients": [ { "clientId": "[REDACTED]", "name": "ALTERED for Raycast", "type": "public", "clientSecret": "[REDACTED]", "icon": "icon.png", "redirectUrls": [ "https://raycast.com/redirect?packageName=Extension" ], "disabled": false, "skipConsent": true, "metadata": { "platform": "raycast" } } ], "scopes": [ "openid", "profile", "email", "offline_access" ] } } }, { "name": "oidc-polyfill", "config": { "id": "oidc-polyfill", "endpoints": {} } } ], "advanced": { "database": {} } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" import { drizzleAdapter } from "better-auth/adapters/drizzle" import { oidcProvider } from "better-auth/plugins" import { db } from "~/server/data/store" export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", usePlural: true }), plugins: [ oidcProvider({ loginPage: "/sign-in", trustedClients: [ { clientId: "altered-launcher", name: "ALTERED for Raycast", type: "public", clientSecret: "placeholder-secret", redirectUrls: ["https://raycast.com/redirect?packageName=Extension"], disabled: false, skipConsent: true, metadata: { platform: "raycast" } } ] }) ] }) ``` ### Additional context **OAuth 2.0 Specification:** According to RFC 6749 (OAuth 2.0), public clients are clients that cannot maintain the confidentiality of their credentials. They use PKCE (RFC 7636) for security instead of client secrets. Public clients should not require client secrets. **Where the issue manifests:** 1. **ID Token signing** (line ~800+ in token endpoint): When `useJWTPlugin: false`, the code uses `client.clientSecret` to sign ID tokens with HS256, even for public clients 2. **Client secret validation**: Some validation paths check for non-empty secrets even for public clients 3. **Type definitions**: The TypeScript types may not properly reflect that `clientSecret` is optional for public clients **Suggested fix:** 1. Make `clientSecret` optional in types for public clients 2. Skip client secret operations for public clients (use different signing method or skip signing) 3. Add proper type guards to check client type before using secrets 4. For ID token signing with public clients, either: - Require JWT plugin (asymmetric keys) - Use a different signing mechanism - Skip signing if no secret is available **Reference:** - BetterAuth OIDC plugin source: https://github.com/better-auth/better-auth/blob/5f89bfa076dbb71959a2582b43d13e49dddf0856/packages/better-auth/src/plugins/oidc-provider/index.ts - Token endpoint: lines ~700-800 (ID token signing logic) - RFC 6749 (OAuth 2.0): https://datatracker.ietf.org/doc/html/rfc6749 - RFC 7636 (PKCE): https://datatracker.ietf.org/doc/html/rfc7636
GiteaMirror added the locked label 2026-04-17 19:11:40 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 10, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3634782582 --> @better-auth-agent[bot] commented on GitHub (Dec 10, 2025): (tag @better-auth-agent if you would like an answer from the Agent) _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 10, 2025):

You're right—BetterAuth's OIDC provider currently requires a non-empty clientSecret for public clients, which contradicts OAuth 2.0 and PKCE standards. The codebase does skip clientSecret validation for public clients during the token exchange, but when signing ID tokens (if useJWTPlugin is false), it always uses client.clientSecret for HS256, causing the "Zero-length key is not supported" error if the secret is missing or empty. This is a known issue and has been reported in several threads (#2813, #2752).

The schema already marks clientSecret as optional for public clients, but the runtime logic doesn't consistently enforce this. The recommended fix is to make clientSecret truly optional for public clients, skip secret-based signing/validation for them, and require asymmetric signing (JWT plugin) or alternative logic for ID tokens if no secret is present (source).

Until this is fixed, the only workaround is to provide a placeholder secret for public clients. If you want to patch locally, you can add a guard to skip HS256 signing for public clients without a secret, or require the JWT plugin for those cases. This would align BetterAuth with OAuth 2.0 and PKCE best practices.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3634790763 --> @dosubot[bot] commented on GitHub (Dec 10, 2025): <!-- Answer --> You're right—BetterAuth's OIDC provider currently requires a non-empty `clientSecret` for public clients, which contradicts OAuth 2.0 and PKCE standards. The codebase does skip clientSecret validation for public clients during the token exchange, but when signing ID tokens (if `useJWTPlugin` is false), it always uses `client.clientSecret` for HS256, causing the "Zero-length key is not supported" error if the secret is missing or empty. This is a known issue and has been reported in several threads ([#2813](https://github.com/better-auth/better-auth/issues/2813), [#2752](https://github.com/better-auth/better-auth/issues/2752)). The schema already marks `clientSecret` as optional for public clients, but the runtime logic doesn't consistently enforce this. The recommended fix is to make `clientSecret` truly optional for public clients, skip secret-based signing/validation for them, and require asymmetric signing (JWT plugin) or alternative logic for ID tokens if no secret is present ([source](https://github.com/better-auth/better-auth/blob/4146cf3fdab5e75dba68bf19f8e64da019a10694/packages/better-auth/src/plugins/oidc-provider/index.ts#L877-L1046)). Until this is fixed, the only workaround is to provide a placeholder secret for public clients. If you want to patch locally, you can add a guard to skip HS256 signing for public clients without a secret, or require the JWT plugin for those cases. This would align BetterAuth with OAuth 2.0 and PKCE best practices. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/73111cda-fce2-4590-80a7-d2ebff3ee55c?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%27re%20right%E2%80%94BetterAuth%27s%20OIDC%20provider%20currently%20requires%20a%20non-empty%20%60clientSecret%60%20for%20public%20clients%2C%20which%20contradicts%20OAuth%202.0%20and%20PKCE%20standards.%20The%20codebase%20does%20skip%20clientSecret%20validation%20for%20public%20clients%20during%20the%20token%20exchange%2C%20but%20when%20signing%20ID%20tokens%20%28if%20%60useJWTPlugin%60%20is%20false%29%2C%20it%20always%20uses%20%60client.clientSecret%60%20for%20HS256%2C%20causing%20the%20%22Zero-length%20key%20is%20not%20supported%22%20error%20if%20the%20secret%20is%20missing%20or%20empty.%20This%20is%20a%20known%20issue%20and%20has%20been%20reported%20in%20several%20threads%20%28%5B%232813%5D%28https%3A//github.com/better-auth/better-auth/issues/2813%29%2C%20%5B%232752%5D%28https%3A//github.com/better-auth/better-auth/issues/2752%29%29.%0A%0AThe%20schema%20already%20marks%20%60clientSecret%60%20as%20optional%20for%20public%20clients%2C%20but%20the%20runtime%20logic%20doesn%27t%20consistently%20enforce%20this.%20The%20recommended%20fix%20is%20to%20make%20%60clientSecret%60%20truly%20optional%20for%20public%20clients%2C%20skip%20secret-based%20signing/validation%20for%20them%2C%20and%20require%20asymmetric%20signing%20%28JWT%20plugin%29%20or%20alternative%20logic%20for%20ID%20tokens%20if%20no%20secret%20is%20present%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/blob/4146cf3fdab5e75dba68bf19f8e64da019a10694/packages/better-auth/src/plugins/oidc-provider/index.ts%23L877-L1046%29%29.%0A%0AUntil%20this%20is%20fixed%2C%20the%20only%20workaround%20is%20to%20provide%20a%20placeholder%20secret%20for%20public%20clients.%20If%20you%20want%20to%20patch%20locally%2C%20you%20can%20add%20a%20guard%20to%20skip%20HS256%20signing%20for%20public%20clients%20without%20a%20secret%2C%20or%20require%20the%20JWT%20plugin%20for%20those%20cases.%20This%20would%20align%20BetterAuth%20with%20OAuth%202.0%20and%20PKCE%20best%20practices.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6651)
Author
Owner

@GautamBytes commented on GitHub (Dec 12, 2025):

Looking into it!

<!-- gh-comment-id:3645650170 --> @GautamBytes commented on GitHub (Dec 12, 2025): Looking into it!
Author
Owner

@dvanmali commented on GitHub (Dec 24, 2025):

Hi all, we released the new OAuth Provider Plugin which allows for optional clientSecret field values which public clients don't have. Feel free to let us know how it works :)

<!-- gh-comment-id:3688569342 --> @dvanmali commented on GitHub (Dec 24, 2025): Hi all, we released the new [OAuth Provider Plugin](https://www.better-auth.com/docs/plugins/oauth-provider) which allows for optional `clientSecret` field values which public clients don't have. Feel free to let us know how it works :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27908