From 4ba09f4a68d26b9729bcfe0989e348dc72e5df14 Mon Sep 17 00:00:00 2001 From: "mini.jeong" Date: Tue, 17 Mar 2026 19:22:16 +0900 Subject: [PATCH 1/4] fix(sso): default tokenEndpointAuthentication to client_secret_post MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better-auth's SSO plugin does not URL-encode credentials before Base64 encoding in client_secret_basic mode (RFC 6749 §2.3.1). When the client secret contains special characters (+, =, /), OIDC providers decode them incorrectly, causing invalid_client errors. Default to client_secret_post when tokenEndpointAuthentication is not explicitly set to avoid this upstream encoding issue. Fixes #3626 --- packages/db/scripts/register-sso-provider.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/db/scripts/register-sso-provider.ts b/packages/db/scripts/register-sso-provider.ts index 07d0ff7684d..1a5df29e933 100644 --- a/packages/db/scripts/register-sso-provider.ts +++ b/packages/db/scripts/register-sso-provider.ts @@ -507,7 +507,11 @@ async function registerSSOProvider(): Promise { clientSecret: ssoConfig.oidcConfig.clientSecret, authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint, tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint, - tokenEndpointAuthentication: ssoConfig.oidcConfig.tokenEndpointAuthentication, + // Default to client_secret_post: better-auth sends client_secret_basic + // credentials without URL-encoding per RFC 6749 §2.3.1, so '+' in secrets + // is decoded as space by OIDC providers, causing invalid_client errors. + tokenEndpointAuthentication: + ssoConfig.oidcConfig.tokenEndpointAuthentication || 'client_secret_post', jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint, pkce: ssoConfig.oidcConfig.pkce, discoveryEndpoint: From f365e1e62430988b5fa31efecdd7b306430f3530 Mon Sep 17 00:00:00 2001 From: "mini.jeong" Date: Tue, 17 Mar 2026 19:47:07 +0900 Subject: [PATCH 2/4] fix(sso): use nullish coalescing and add env var for tokenEndpointAuthentication - Use ?? instead of || for semantic correctness - Add SSO_OIDC_TOKEN_ENDPOINT_AUTH env var so users can explicitly set client_secret_basic when their provider requires it --- packages/db/scripts/register-sso-provider.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/db/scripts/register-sso-provider.ts b/packages/db/scripts/register-sso-provider.ts index 1a5df29e933..32e341352de 100644 --- a/packages/db/scripts/register-sso-provider.ts +++ b/packages/db/scripts/register-sso-provider.ts @@ -215,6 +215,10 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null { pkce: process.env.SSO_OIDC_PKCE !== 'false', authorizationEndpoint: process.env.SSO_OIDC_AUTHORIZATION_ENDPOINT, tokenEndpoint: process.env.SSO_OIDC_TOKEN_ENDPOINT, + tokenEndpointAuthentication: process.env.SSO_OIDC_TOKEN_ENDPOINT_AUTH as + | 'client_secret_post' + | 'client_secret_basic' + | undefined, userInfoEndpoint: process.env.SSO_OIDC_USERINFO_ENDPOINT, jwksEndpoint: process.env.SSO_OIDC_JWKS_ENDPOINT, discoveryEndpoint: @@ -511,7 +515,7 @@ async function registerSSOProvider(): Promise { // credentials without URL-encoding per RFC 6749 §2.3.1, so '+' in secrets // is decoded as space by OIDC providers, causing invalid_client errors. tokenEndpointAuthentication: - ssoConfig.oidcConfig.tokenEndpointAuthentication || 'client_secret_post', + ssoConfig.oidcConfig.tokenEndpointAuthentication ?? 'client_secret_post', jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint, pkce: ssoConfig.oidcConfig.pkce, discoveryEndpoint: From ac0d326e9580b63c321e21c44f67824ab2022dd9 Mon Sep 17 00:00:00 2001 From: "mini.jeong" Date: Tue, 17 Mar 2026 19:49:56 +0900 Subject: [PATCH 3/4] docs(sso): add SSO_OIDC_TOKEN_ENDPOINT_AUTH to script usage comment Signed-off-by: Mini Jeong --- packages/db/scripts/register-sso-provider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/db/scripts/register-sso-provider.ts b/packages/db/scripts/register-sso-provider.ts index 32e341352de..8f8863bcbbd 100644 --- a/packages/db/scripts/register-sso-provider.ts +++ b/packages/db/scripts/register-sso-provider.ts @@ -21,6 +21,7 @@ * SSO_OIDC_CLIENT_ID=your_client_id * SSO_OIDC_CLIENT_SECRET=your_client_secret * SSO_OIDC_SCOPES=openid,profile,email (optional) + * SSO_OIDC_TOKEN_ENDPOINT_AUTH=client_secret_post|client_secret_basic (optional, defaults to client_secret_post) * * SAML Providers: * SSO_SAML_ENTRY_POINT=https://your-idp/sso From f3c13358b24ae15abd08f08c3f17f966a4a2a8da Mon Sep 17 00:00:00 2001 From: "mini.jeong" Date: Tue, 17 Mar 2026 21:16:45 +0900 Subject: [PATCH 4/4] fix(sso): validate SSO_OIDC_TOKEN_ENDPOINT_AUTH env var value Replace unsafe `as` type cast with runtime validation to ensure only 'client_secret_post' or 'client_secret_basic' are accepted. Invalid values (typos, empty strings) now fall back to undefined, letting the downstream ?? fallback apply correctly. Signed-off-by: Mini Jeong --- packages/db/scripts/register-sso-provider.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/db/scripts/register-sso-provider.ts b/packages/db/scripts/register-sso-provider.ts index 8f8863bcbbd..ed00894efce 100644 --- a/packages/db/scripts/register-sso-provider.ts +++ b/packages/db/scripts/register-sso-provider.ts @@ -216,10 +216,11 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null { pkce: process.env.SSO_OIDC_PKCE !== 'false', authorizationEndpoint: process.env.SSO_OIDC_AUTHORIZATION_ENDPOINT, tokenEndpoint: process.env.SSO_OIDC_TOKEN_ENDPOINT, - tokenEndpointAuthentication: process.env.SSO_OIDC_TOKEN_ENDPOINT_AUTH as - | 'client_secret_post' - | 'client_secret_basic' - | undefined, + tokenEndpointAuthentication: + process.env.SSO_OIDC_TOKEN_ENDPOINT_AUTH === 'client_secret_post' || + process.env.SSO_OIDC_TOKEN_ENDPOINT_AUTH === 'client_secret_basic' + ? process.env.SSO_OIDC_TOKEN_ENDPOINT_AUTH + : undefined, userInfoEndpoint: process.env.SSO_OIDC_USERINFO_ENDPOINT, jwksEndpoint: process.env.SSO_OIDC_JWKS_ENDPOINT, discoveryEndpoint: