From c682ac86056264b8a203bffcdd6abd2640ea2b42 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Tue, 3 Feb 2026 15:35:45 -0800 Subject: [PATCH] fix(generic-oauth): emit duplicate id warning (#7779) (cherry picked from commit 9f40c35ee428b0313c90089d857b219b911a51ca) (cherry picked from commit d69802ec6a7a1c1b38d946fd090c579cc1e8b203) --- .../generic-oauth/generic-oauth.test.ts | 170 +++++++++++++++++- .../src/plugins/generic-oauth/index.ts | 17 ++ 2 files changed, 186 insertions(+), 1 deletion(-) 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 5420d7c442..5fd3d13e31 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 @@ -2,7 +2,7 @@ import type { GenericEndpointContext } from "@better-auth/core"; import { runWithEndpointContext } from "@better-auth/core/context"; import { betterFetch } from "@better-fetch/fetch"; import { OAuth2Server } from "oauth2-mock-server"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { createAuthClient } from "../../client"; import { parseSetCookieHeader } from "../../cookies"; import { getTestInstance } from "../../test-utils/test-instance"; @@ -1668,6 +1668,174 @@ describe("oauth2", async () => { expect(session.data?.user.image).toBe(customUserInfo.avatar_url); }); + describe("Duplicate Provider ID Detection", () => { + it("should warn when duplicate provider IDs are detected", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await getTestInstance({ + plugins: [ + genericOAuth({ + config: [ + { + providerId: "duplicate-id", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-1", + clientSecret: "secret-1", + }, + { + providerId: "duplicate-id", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-2", + clientSecret: "secret-2", + }, + ], + }), + ], + }); + + expect(warnSpy).toHaveBeenCalledWith( + "Duplicate provider IDs found: duplicate-id", + ); + + warnSpy.mockRestore(); + }); + + it("should warn about multiple duplicate provider IDs", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await getTestInstance({ + plugins: [ + genericOAuth({ + config: [ + { + providerId: "dup-1", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-1", + clientSecret: "secret-1", + }, + { + providerId: "dup-1", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-2", + clientSecret: "secret-2", + }, + { + providerId: "dup-2", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-3", + clientSecret: "secret-3", + }, + { + providerId: "dup-2", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-4", + clientSecret: "secret-4", + }, + ], + }), + ], + }); + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining("Duplicate provider IDs found:"), + ); + const warningMessage = warnSpy.mock.calls[0]?.[0] as string; + expect(warningMessage).toContain("dup-1"); + expect(warningMessage).toContain("dup-2"); + + warnSpy.mockRestore(); + }); + + it("should not warn when all provider IDs are unique", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await getTestInstance({ + plugins: [ + genericOAuth({ + config: [ + { + providerId: "unique-1", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-1", + clientSecret: "secret-1", + }, + { + providerId: "unique-2", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-2", + clientSecret: "secret-2", + }, + ], + }), + ], + }); + + expect(warnSpy).not.toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it("should not warn when only one provider is configured", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await getTestInstance({ + plugins: [ + genericOAuth({ + config: [ + { + providerId: "single-provider", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-1", + clientSecret: "secret-1", + }, + ], + }), + ], + }); + + expect(warnSpy).not.toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); + + it("should warn when provider ID appears more than twice", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await getTestInstance({ + plugins: [ + genericOAuth({ + config: [ + { + providerId: "triple-dup", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-1", + clientSecret: "secret-1", + }, + { + providerId: "triple-dup", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-2", + clientSecret: "secret-2", + }, + { + providerId: "triple-dup", + discoveryUrl: `http://localhost:${port}/.well-known/openid-configuration`, + clientId: "client-3", + clientSecret: "secret-3", + }, + ], + }), + ], + }); + + expect(warnSpy).toHaveBeenCalledWith( + "Duplicate provider IDs found: triple-dup", + ); + + warnSpy.mockRestore(); + }); + }); + describe("RFC 9207 Issuer Validation", () => { it("should allow callback when iss parameter matches configured issuer", async () => { server.service.once("beforeUserinfo", (userInfoResponse) => { diff --git a/packages/better-auth/src/plugins/generic-oauth/index.ts b/packages/better-auth/src/plugins/generic-oauth/index.ts index 9d94772d0c..2403180a31 100644 --- a/packages/better-auth/src/plugins/generic-oauth/index.ts +++ b/packages/better-auth/src/plugins/generic-oauth/index.ts @@ -45,6 +45,23 @@ export type BaseOAuthProviderOptions = Omit< * A generic OAuth plugin that can be used to add OAuth support to any provider */ export const genericOAuth = (options: GenericOAuthOptions) => { + const seenIds = new Set(); + const nonUniqueIds = new Set(); + + for (const config of options.config) { + const id = config.providerId; + if (seenIds.has(id)) { + nonUniqueIds.add(id); + } + seenIds.add(id); + } + + if (nonUniqueIds.size > 0) { + console.warn( + `Duplicate provider IDs found: ${Array.from(nonUniqueIds).join(", ")}`, + ); + } + return { id: "generic-oauth", init: (ctx: AuthContext) => {