mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-24 08:01:56 -05:00
fix(generic-oauth): use discovery userinfo endpoint instead of hardcoded URLs (#8223)
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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<OAuth2UserInfo | null> => {
|
||||
const userInfoUrl = `https://${domain}/userinfo`;
|
||||
|
||||
const { data: profile, error } = await betterFetch<Auth0Profile>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<OAuth2UserInfo | null> => {
|
||||
// Construct userinfo URL from issuer
|
||||
const userInfoUrl = `${issuer}/protocol/openid-connect/userinfo`;
|
||||
|
||||
const { data: profile, error } = await betterFetch<KeycloakProfile>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<OAuth2UserInfo | null> => {
|
||||
const userInfoUrl = `${issuer}/oauth2/v1/userinfo`;
|
||||
|
||||
const { data: profile, error } = await betterFetch<OktaProfile>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user