From feb83a7ba3082ee59cf89988ae2ce63dfefa3dfb Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sun, 1 Mar 2026 16:13:35 +0900 Subject: [PATCH] fix(generic-oauth): use discovery userinfo endpoint instead of hardcoded URLs (#8223) --- .../generic-oauth/generic-oauth.test.ts | 9 ++-- .../plugins/generic-oauth/providers/auth0.ts | 41 ----------------- .../generic-oauth/providers/keycloak.ts | 44 ------------------- .../plugins/generic-oauth/providers/okta.ts | 41 ----------------- 4 files changed, 3 insertions(+), 132 deletions(-) diff --git a/packages/better-auth/src/plugins/generic-oauth/generic-oauth.test.ts b/packages/better-auth/src/plugins/generic-oauth/generic-oauth.test.ts index e30f25de0c..451a0c9810 100644 --- a/packages/better-auth/src/plugins/generic-oauth/generic-oauth.test.ts +++ b/packages/better-auth/src/plugins/generic-oauth/generic-oauth.test.ts @@ -1045,8 +1045,7 @@ describe("oauth2", async () => { expect(oktaConfig.scopes).toEqual(["openid", "profile", "email"]); expect(oktaConfig.clientId).toBe("okta-client-id"); expect(oktaConfig.clientSecret).toBe("okta-client-secret"); - expect(oktaConfig.getUserInfo).toBeDefined(); - expect(typeof oktaConfig.getUserInfo).toBe("function"); + expect(oktaConfig.getUserInfo).toBeUndefined(); }); it("should handle issuer with trailing slash", () => { @@ -1101,8 +1100,7 @@ describe("oauth2", async () => { expect(auth0Config.scopes).toEqual(["openid", "profile", "email"]); expect(auth0Config.clientId).toBe("auth0-client-id"); expect(auth0Config.clientSecret).toBe("auth0-client-secret"); - expect(auth0Config.getUserInfo).toBeDefined(); - expect(typeof auth0Config.getUserInfo).toBe("function"); + expect(auth0Config.getUserInfo).toBeUndefined(); }); it("should handle domain with protocol prefix", () => { @@ -1267,8 +1265,7 @@ describe("oauth2", async () => { expect(keycloakConfig.scopes).toEqual(["openid", "profile", "email"]); expect(keycloakConfig.clientId).toBe("keycloak-client-id"); expect(keycloakConfig.clientSecret).toBe("keycloak-client-secret"); - expect(keycloakConfig.getUserInfo).toBeDefined(); - expect(typeof keycloakConfig.getUserInfo).toBe("function"); + expect(keycloakConfig.getUserInfo).toBeUndefined(); }); it("should handle issuer with trailing slash", () => { diff --git a/packages/better-auth/src/plugins/generic-oauth/providers/auth0.ts b/packages/better-auth/src/plugins/generic-oauth/providers/auth0.ts index f8645d322b..e2ef2c921c 100644 --- a/packages/better-auth/src/plugins/generic-oauth/providers/auth0.ts +++ b/packages/better-auth/src/plugins/generic-oauth/providers/auth0.ts @@ -1,5 +1,3 @@ -import type { OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2"; -import { betterFetch } from "@better-fetch/fetch"; import type { BaseOAuthProviderOptions, GenericOAuthConfig } from "../index"; export interface Auth0Options extends BaseOAuthProviderOptions { @@ -10,17 +8,6 @@ export interface Auth0Options extends BaseOAuthProviderOptions { domain: string; } -interface Auth0Profile { - sub: string; - name?: string; - email?: string; - email_verified?: boolean; - picture?: string; - nickname?: string; - given_name?: string; - family_name?: string; -} - /** * Auth0 OAuth provider helper * @@ -50,33 +37,6 @@ export function auth0(options: Auth0Options): GenericOAuthConfig { const domain = options.domain.replace(/^https?:\/\//, ""); const discoveryUrl = `https://${domain}/.well-known/openid-configuration`; - const getUserInfo = async ( - tokens: OAuth2Tokens, - ): Promise => { - const userInfoUrl = `https://${domain}/userinfo`; - - const { data: profile, error } = await betterFetch( - userInfoUrl, - { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }, - ); - - if (error || !profile) { - return null; - } - - return { - id: profile.sub, - name: profile.name ?? profile.nickname ?? undefined, - email: profile.email ?? undefined, - image: profile.picture, - emailVerified: profile.email_verified ?? false, - }; - }; - return { providerId: "auth0", discoveryUrl, @@ -88,6 +48,5 @@ export function auth0(options: Auth0Options): GenericOAuthConfig { disableImplicitSignUp: options.disableImplicitSignUp, disableSignUp: options.disableSignUp, overrideUserInfo: options.overrideUserInfo, - getUserInfo, }; } diff --git a/packages/better-auth/src/plugins/generic-oauth/providers/keycloak.ts b/packages/better-auth/src/plugins/generic-oauth/providers/keycloak.ts index 4c2fa53aca..eaff57564d 100644 --- a/packages/better-auth/src/plugins/generic-oauth/providers/keycloak.ts +++ b/packages/better-auth/src/plugins/generic-oauth/providers/keycloak.ts @@ -1,5 +1,3 @@ -import type { OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2"; -import { betterFetch } from "@better-fetch/fetch"; import type { BaseOAuthProviderOptions, GenericOAuthConfig } from "../index"; export interface KeycloakOptions extends BaseOAuthProviderOptions { @@ -10,17 +8,6 @@ export interface KeycloakOptions extends BaseOAuthProviderOptions { issuer: string; } -interface KeycloakProfile { - sub: string; - name?: string; - email?: string; - email_verified?: boolean; - picture?: string; - preferred_username?: string; - given_name?: string; - family_name?: string; -} - /** * Keycloak OAuth provider helper * @@ -50,36 +37,6 @@ export function keycloak(options: KeycloakOptions): GenericOAuthConfig { const issuer = options.issuer.replace(/\/$/, ""); const discoveryUrl = `${issuer}/.well-known/openid-configuration`; - const getUserInfo = async ( - tokens: OAuth2Tokens, - ): Promise => { - // Construct userinfo URL from issuer - const userInfoUrl = `${issuer}/protocol/openid-connect/userinfo`; - - const { data: profile, error } = await betterFetch( - userInfoUrl, - { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }, - ); - - if (error || !profile) { - return null; - } - - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username ?? undefined, - email: profile.email ?? undefined, - image: profile.picture, - // Keycloak provides email_verified per OIDC standard, but availability depends on configuration. - // We default to false when not provided or not configured. - emailVerified: profile.email_verified ?? false, - }; - }; - return { providerId: "keycloak", discoveryUrl, @@ -91,6 +48,5 @@ export function keycloak(options: KeycloakOptions): GenericOAuthConfig { disableImplicitSignUp: options.disableImplicitSignUp, disableSignUp: options.disableSignUp, overrideUserInfo: options.overrideUserInfo, - getUserInfo, }; } diff --git a/packages/better-auth/src/plugins/generic-oauth/providers/okta.ts b/packages/better-auth/src/plugins/generic-oauth/providers/okta.ts index 3711fb519f..ceca158279 100644 --- a/packages/better-auth/src/plugins/generic-oauth/providers/okta.ts +++ b/packages/better-auth/src/plugins/generic-oauth/providers/okta.ts @@ -1,5 +1,3 @@ -import type { OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2"; -import { betterFetch } from "@better-fetch/fetch"; import type { BaseOAuthProviderOptions, GenericOAuthConfig } from "../index"; export interface OktaOptions extends BaseOAuthProviderOptions { @@ -10,17 +8,6 @@ export interface OktaOptions extends BaseOAuthProviderOptions { issuer: string; } -interface OktaProfile { - sub: string; - name?: string; - email?: string; - email_verified?: boolean; - picture?: string; - preferred_username?: string; - given_name?: string; - family_name?: string; -} - /** * Okta OAuth provider helper * @@ -50,33 +37,6 @@ export function okta(options: OktaOptions): GenericOAuthConfig { const issuer = options.issuer.replace(/\/$/, ""); const discoveryUrl = `${issuer}/.well-known/openid-configuration`; - const getUserInfo = async ( - tokens: OAuth2Tokens, - ): Promise => { - const userInfoUrl = `${issuer}/oauth2/v1/userinfo`; - - const { data: profile, error } = await betterFetch( - userInfoUrl, - { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }, - ); - - if (error || !profile) { - return null; - } - - return { - id: profile.sub, - name: profile.name ?? profile.preferred_username ?? undefined, - email: profile.email ?? undefined, - image: profile.picture, - emailVerified: profile.email_verified ?? false, - }; - }; - return { providerId: "okta", discoveryUrl, @@ -88,6 +48,5 @@ export function okta(options: OktaOptions): GenericOAuthConfig { disableImplicitSignUp: options.disableImplicitSignUp, disableSignUp: options.disableSignUp, overrideUserInfo: options.overrideUserInfo, - getUserInfo, }; }