From d64174ec86434375cb7e010ee4dfcd031e20c821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Wed, 27 May 2026 15:18:16 +0200 Subject: [PATCH] fix(oauth-provider): hide DCR endpoint unless enabled (#9448) Co-authored-by: Gustavo Valverde --- .changeset/giant-canyons-report.md | 5 +++ packages/oauth-provider/src/metadata.test.ts | 44 ++++++++++++++++++++ packages/oauth-provider/src/metadata.ts | 6 ++- packages/oauth-provider/src/oauth.ts | 4 ++ packages/oauth-provider/src/types/oauth.ts | 4 +- 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 .changeset/giant-canyons-report.md diff --git a/.changeset/giant-canyons-report.md b/.changeset/giant-canyons-report.md new file mode 100644 index 0000000000..b31a461c81 --- /dev/null +++ b/.changeset/giant-canyons-report.md @@ -0,0 +1,5 @@ +--- +"@better-auth/oauth-provider": patch +--- + +Remove registration_endpoint on .well-known config if `allowDynamicClientRegistration` is not true diff --git a/packages/oauth-provider/src/metadata.test.ts b/packages/oauth-provider/src/metadata.test.ts index 0ff8b27a71..a76e926029 100644 --- a/packages/oauth-provider/src/metadata.test.ts +++ b/packages/oauth-provider/src/metadata.test.ts @@ -53,6 +53,7 @@ describe("oauth metadata", async () => { oauthAuthServerConfig: true, openidConfig: true, }, + allowDynamicClientRegistration: true, ...opts?.oauthProviderConfig, }), ...(opts?.oauthProviderConfig?.disableJwtPlugin @@ -154,6 +155,25 @@ describe("oauth metadata", async () => { expect(metadata.issuer).toBe(baseURL); }); + it("should advertise dynamic client registration from direct OAuth metadata when enabled", async () => { + const { customFetchImpl } = await createTestInstance({ + oauthProviderConfig: { + scopes: ["create:test"], + allowDynamicClientRegistration: true, + }, + }); + const response = await customFetchImpl( + `${baseURL}/.well-known/oauth-authorization-server`, + { method: "GET" }, + ); + + expect(response.status).toBe(200); + const metadata = (await response.json()) as { + registration_endpoint?: string; + }; + expect(metadata.registration_endpoint).toBe(`${baseURL}/oauth2/register`); + }); + it("should serve OIDC metadata at the direct issuer well-known URL", async () => { const { customFetchImpl } = await createTestInstance(); const response = await customFetchImpl( @@ -263,6 +283,30 @@ describe("oauth metadata", async () => { }); }); + it("should not provide dynamic client registration endpoint when disabled", async () => { + const { auth } = await createTestInstance({ + oauthProviderConfig: { + allowDynamicClientRegistration: false, + }, + }); + const metadata = await auth.api.getOpenIdConfig(); + expect(metadata.registration_endpoint).toBeUndefined(); + const oauthMetadata = await auth.api.getOAuthServerConfig(); + expect(oauthMetadata.registration_endpoint).toBeUndefined(); + }); + + it("should not provide dynamic client registration endpoint when undefined", async () => { + const { auth } = await createTestInstance({ + oauthProviderConfig: { + allowDynamicClientRegistration: undefined, + }, + }); + const metadata = await auth.api.getOpenIdConfig(); + expect(metadata.registration_endpoint).toBeUndefined(); + const oauthMetadata = await auth.api.getOAuthServerConfig(); + expect(oauthMetadata.registration_endpoint).toBeUndefined(); + }); + it("should utilize advertised metadata fields", async () => { const advertisedScopes = ["email"]; const advertisedClaims = ["sub", "iss", "aud", "exp", "iat", "scope"]; diff --git a/packages/oauth-provider/src/metadata.ts b/packages/oauth-provider/src/metadata.ts index 13f025942a..7292ea38e1 100644 --- a/packages/oauth-provider/src/metadata.ts +++ b/packages/oauth-provider/src/metadata.ts @@ -15,6 +15,7 @@ export function authServerMetadata( opts?: JwtOptions, overrides?: { scopes_supported?: AuthServerMetadata["scopes_supported"]; + dynamic_client_registration_supported?: boolean; public_client_supported?: boolean; grant_types_supported?: GrantType[]; jwt_disabled?: boolean; @@ -30,7 +31,9 @@ export function authServerMetadata( ? undefined : (opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`), - registration_endpoint: `${baseURL}/oauth2/register`, + registration_endpoint: overrides?.dynamic_client_registration_supported + ? `${baseURL}/oauth2/register` + : undefined, introspection_endpoint: `${baseURL}/oauth2/introspect`, revocation_endpoint: `${baseURL}/oauth2/revoke`, response_types_supported: @@ -75,6 +78,7 @@ export function oidcServerMetadata( : getJwtPlugin(ctx.context).options; const authMetadata = authServerMetadata(ctx, jwtPluginOptions, { scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes, + dynamic_client_registration_supported: opts.allowDynamicClientRegistration, public_client_supported: opts.allowUnauthenticatedClientRegistration, grant_types_supported: opts.grantTypes, jwt_disabled: opts.disableJwtPlugin, diff --git a/packages/oauth-provider/src/oauth.ts b/packages/oauth-provider/src/oauth.ts index 122eaa3424..69eb2f1c88 100644 --- a/packages/oauth-provider/src/oauth.ts +++ b/packages/oauth-provider/src/oauth.ts @@ -240,6 +240,8 @@ export const oauthProvider = >(options: O) => { const metadata = authServerMetadata(endpointCtx, jwtPluginOptions, { scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes, + dynamic_client_registration_supported: + opts.allowDynamicClientRegistration, public_client_supported: opts.allowUnauthenticatedClientRegistration, grant_types_supported: opts.grantTypes, jwt_disabled: opts.disableJwtPlugin, @@ -437,6 +439,8 @@ export const oauthProvider = >(options: O) => { const authMetadata = authServerMetadata(ctx, jwtPluginOptions, { scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes, + dynamic_client_registration_supported: + opts.allowDynamicClientRegistration, public_client_supported: opts.allowUnauthenticatedClientRegistration, grant_types_supported: opts.grantTypes, diff --git a/packages/oauth-provider/src/types/oauth.ts b/packages/oauth-provider/src/types/oauth.ts index 7de56189db..c2cf8ddd8a 100644 --- a/packages/oauth-provider/src/types/oauth.ts +++ b/packages/oauth-provider/src/types/oauth.ts @@ -61,9 +61,11 @@ export interface AuthServerMetadata { /** * The URL of the dynamic client registration endpoint. * + * This field is only present when `allowDynamicClientRegistration` is enabled. + * * @default `/oauth2/register` */ - registration_endpoint: string; + registration_endpoint?: string; /** * Supported scopes. */