From 132e293d7a82db30d7d1a63fb32c28df863204ae Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Fri, 19 Jun 2026 08:55:26 -0700 Subject: [PATCH] fix(oauth-provider): redirect missing `response_type` errors (#10149) --- .changeset/authorize-missing-response-type.md | 5 ++ packages/oauth-provider/src/authorize.ts | 7 +-- .../src/oauth-endpoint.integration.test.ts | 46 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .changeset/authorize-missing-response-type.md diff --git a/.changeset/authorize-missing-response-type.md b/.changeset/authorize-missing-response-type.md new file mode 100644 index 0000000000..163b46ca2d --- /dev/null +++ b/.changeset/authorize-missing-response-type.md @@ -0,0 +1,5 @@ +--- +"@better-auth/oauth-provider": patch +--- + +OAuth Provider authorization requests that omit `response_type` now return `invalid_request` to the verified client redirect URI instead of falling back to the provider error page. diff --git a/packages/oauth-provider/src/authorize.ts b/packages/oauth-provider/src/authorize.ts index 230d312d70..198e9be706 100644 --- a/packages/oauth-provider/src/authorize.ts +++ b/packages/oauth-provider/src/authorize.ts @@ -388,10 +388,11 @@ export async function authorizeEndpoint( } if (!query.response_type) { - return handleRedirect( + return authorizeRedirectOnError(opts)({ + error: "invalid_request", + error_description: "response_type is required", ctx, - getErrorURL(ctx, "invalid_request", "response_type is required"), - ); + }); } const promptSet = ctx.query?.prompt diff --git a/packages/oauth-provider/src/oauth-endpoint.integration.test.ts b/packages/oauth-provider/src/oauth-endpoint.integration.test.ts index 7564416987..1f6033bc80 100644 --- a/packages/oauth-provider/src/oauth-endpoint.integration.test.ts +++ b/packages/oauth-provider/src/oauth-endpoint.integration.test.ts @@ -236,6 +236,52 @@ describe("RFC envelope compliance across OAuth endpoints", async () => { ); }); + it("missing response_type with trusted redirect_uri → invalid_request", async () => { + if (!oauthClient?.client_id) throw new Error("beforeAll didn't run"); + const state = "missing-response-type"; + const qs = new URLSearchParams({ + client_id: oauthClient.client_id, + redirect_uri: redirectUri, + state, + }).toString(); + const { status, location } = await captureRedirect( + `/oauth2/authorize?${qs}`, + ); + expect(status).toBeGreaterThanOrEqual(300); + expect(status).toBeLessThan(400); + const redirectLocation = expectRedirectLocation(location); + expect(redirectLocation.startsWith(redirectUri)).toBe(true); + const errorUrl = new URL(redirectLocation); + expect(errorUrl.searchParams.get("error")).toBe("invalid_request"); + expect(errorUrl.searchParams.get("error_description")).toMatch( + /response_type/, + ); + expect(errorUrl.searchParams.get("state")).toBe(state); + expect(errorUrl.searchParams.get("iss")).toBeTruthy(); + }); + + it("missing response_type with unregistered redirect_uri → server error page", async () => { + if (!oauthClient?.client_id) throw new Error("beforeAll didn't run"); + const qs = new URLSearchParams({ + client_id: oauthClient.client_id, + redirect_uri: "https://evil.example.com/callback", + state: "s", + }).toString(); + const { status, location } = await captureRedirect( + `/oauth2/authorize?${qs}`, + ); + expect(status).toBeGreaterThanOrEqual(300); + expect(status).toBeLessThan(400); + const redirectLocation = expectRedirectLocation(location); + expect(redirectLocation.startsWith(redirectUri)).toBe(false); + expect(redirectLocation).not.toContain("evil.example.com"); + const errorUrl = new URL(redirectLocation); + expect(errorUrl.searchParams.get("error")).toBe("invalid_request"); + expect(errorUrl.searchParams.get("error_description")).toMatch( + /response_type/, + ); + }); + it("missing client_id → server error page with invalid_request (no RP to trust)", async () => { const qs = new URLSearchParams({ redirect_uri: redirectUri,