[GH-ISSUE #2199] Keycloak multi-tenant OAuth support #26422

Closed
opened 2026-04-17 16:59:28 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @pauldunn on GitHub (Apr 9, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2199

Can I dynamically provide the clientSecret and realmId during the configuration of genericOauth?

In the example below, I’ve hardcoded the values to test Better-Auth with my Keycloak instance, and it’s functioning well. Now, I’d like to enable users to input these details through a form, which I can then pass into Better-Auth to initiate the Keycloak authentication flow.

import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";

const realmId = "uuid here..";
const clientSecret = "secret here..";

export const auth = betterAuth({
  plugins: [
    genericOAuth({
      config: [
        {
          providerId: "keycloak",
          clientId: process.env.KEYCLOAK_CLIENT as string,
          clientSecret: clientSecret,
          discoveryUrl: `${process.env.NEXT_PUBLIC_API_URL_KEYCLOAK}/realms/${realmId}/.well-known/openid-configuration`,
          scopes: ["roles", "profile"],
        },
      ],
    }),
  ],
});
Originally created by @pauldunn on GitHub (Apr 9, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2199 Can I dynamically provide the clientSecret and realmId during the configuration of genericOauth? In the example below, I’ve hardcoded the values to test Better-Auth with my Keycloak instance, and it’s functioning well. Now, I’d like to enable users to input these details through a form, which I can then pass into Better-Auth to initiate the Keycloak authentication flow. ``` import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; const realmId = "uuid here.."; const clientSecret = "secret here.."; export const auth = betterAuth({ plugins: [ genericOAuth({ config: [ { providerId: "keycloak", clientId: process.env.KEYCLOAK_CLIENT as string, clientSecret: clientSecret, discoveryUrl: `${process.env.NEXT_PUBLIC_API_URL_KEYCLOAK}/realms/${realmId}/.well-known/openid-configuration`, scopes: ["roles", "profile"], }, ], }), ], }); ```
GiteaMirror added the locked label 2026-04-17 16:59:28 -05:00
Author
Owner

@Bekacru commented on GitHub (Apr 10, 2025):

You should look into the SSO plugin.
https://www.better-auth.com/docs/plugins/sso

<!-- gh-comment-id:2793577323 --> @Bekacru commented on GitHub (Apr 10, 2025): You should look into the SSO plugin. https://www.better-auth.com/docs/plugins/sso
Author
Owner

@gobengo commented on GitHub (May 10, 2025):

@Bekacru The SSO plugin doesn't actually provide the same features, though. It doesn't have the equivalent of the genericOauth plugin's affordances for linking oauth accounts. I too would like to be able to use the oauth2 linking functionality, but on provider id that is not statically configured in the src code, but instead added at runtime.

Is there an architectural reason why all the configured genericOauth providers must be passed as a static array?

Instead, if it worked with a plugin option getConfigForProvider(providerId: string): Promise<OauthConfig>, and that was called with whatever providerId is passed to authClient.oauth2.link or authClient.signIn.oauth2, that would allow for the client id to be pulled async from a db, or even dynamically registered with the provider just-in-time. Any reason that wouldn't work?

Upon quick glance, maybe it would just need this line to support async resolving the providerId to a config instead of only looking in the static plugin options. 8fcc758fe9/packages/better-auth/src/plugins/generic-oauth/index.ts (L402)

<!-- gh-comment-id:2868999811 --> @gobengo commented on GitHub (May 10, 2025): @Bekacru The SSO plugin doesn't actually provide the same features, though. It doesn't have the equivalent of the genericOauth plugin's affordances for [linking oauth accounts](https://www.better-auth.com/docs/plugins/generic-oauth#linking-oauth-accounts). I too would like to be able to use the oauth2 linking functionality, but on provider id that is not statically configured in the src code, but instead added at runtime. Is there an architectural reason why all the configured genericOauth providers must be passed as a static array? Instead, if it worked with a plugin option `getConfigForProvider(providerId: string): Promise<OauthConfig>`, and that was called with whatever providerId is passed to `authClient.oauth2.link` or `authClient.signIn.oauth2`, that would allow for the client id to be pulled async from a db, or even dynamically registered with the provider just-in-time. Any reason that wouldn't work? Upon quick glance, maybe it would just need this line to support async resolving the providerId to a config instead of only looking in the static plugin options. https://github.com/better-auth/better-auth/blob/8fcc758fe98640be2fc24f89e7c6259be8a52cec/packages/better-auth/src/plugins/generic-oauth/index.ts#L402
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#26422