fix(oauth-provider): hide DCR endpoint unless enabled (#9448)

Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com>
This commit is contained in:
Daniel Müller
2026-05-27 15:18:16 +02:00
committed by GitHub
parent 8401d11f43
commit d64174ec86
5 changed files with 61 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
---
"@better-auth/oauth-provider": patch
---
Remove registration_endpoint on .well-known config if `allowDynamicClientRegistration` is not true

View File

@@ -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"];

View File

@@ -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,

View File

@@ -240,6 +240,8 @@ export const oauthProvider = <O extends OAuthOptions<Scope[]>>(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 = <O extends OAuthOptions<Scope[]>>(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,

View File

@@ -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.
*/