fix(oauth-provider): report an unknown client in a client assertion as not found (#11811)

This commit is contained in:
Gustavo Valverde
2026-10-15 13:11:39 +01:00
committed by GitHub
parent bb5edcf9f8
commit 61c913da73
4 changed files with 25 additions and 3 deletions
@@ -2,4 +2,4 @@
"@better-auth/oauth-provider": patch
---
With `clientRegistrationDefaultApplicationType: "infer"`, clients that register an `http` callback on `localhost`, `127.0.0.1`, or `[::1]` without `application_type` now register as `native`. This includes older Claude Code releases, which register `http://127.0.0.1:<port>/callback`. Current Claude Code releases send `application_type: "native"` and were not affected. Before, `"infer"` chose `native` only for custom-scheme callbacks, so these registrations were treated as `web` and rejected with `invalid_redirect_uri`. The `"web"` default and the `"native"` setting are unchanged, and an `application_type` the client sends is never overridden.
With `clientRegistrationDefaultApplicationType: "infer"`, clients that register an `http` callback on `localhost`, `127.0.0.1`, or `[::1]` without `application_type` now register as `native`. This includes older Claude Code releases, which register `http://127.0.0.1:<port>/callback`. Current Claude Code releases send `application_type: "native"` and were not affected. Before, `"infer"` chose `native` only for callbacks whose scheme is not `http` or `https`, so these registrations were treated as `web` and rejected with `invalid_redirect_uri`. The `"web"` default and the `"native"` setting are unchanged, and an `application_type` the client sends is never overridden.
@@ -2,4 +2,4 @@
"@better-auth/oauth-provider": patch
---
Requests that name a `client_id` the server does not know now fail with `error_description` set to `client not found`. Before, `/oauth2/authorize` said `client_id is required`, which read as a missing parameter, and `/oauth2/token`, `/oauth2/revoke`, and `/oauth2/introspect` said `missing client`. You would see this, for example, when an MCP client kept a registration the server had deleted. The `invalid_client` error code is unchanged.
Requests that name a `client_id` the server does not know now fail with `error_description` set to `client not found`. Before, `/oauth2/authorize` said `client_id is required`, which read as a missing parameter, `/oauth2/token`, `/oauth2/revoke`, and `/oauth2/introspect` said `missing client`, and requests that authenticate with a `private_key_jwt` client assertion said `unknown client`. You would see this, for example, when an MCP client kept a registration the server had deleted. The `invalid_client` error code is unchanged.
@@ -645,6 +645,28 @@ describe("private_key_jwt authentication", async () => {
expect(result.error?.status).toBeLessThan(500);
});
/**
* @see https://github.com/croffasia/itsaplan/issues/564
*/
it("reports an assertion for an unknown client as a client that was not found", async () => {
const result = await client.$fetch("/oauth2/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: "no-such-client",
client_assertion_type:
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: await signAssertion({ clientId: "no-such-client" }),
}),
headers: { "content-type": "application/x-www-form-urlencoded" },
});
expect(result.error).toMatchObject({
error: "invalid_client",
error_description: "client not found",
});
});
it("should reject expired assertion", async () => {
const now = Math.floor(Date.now() / 1000);
const codeVerifier = generateRandomString(32);
@@ -532,7 +532,7 @@ export async function verifyClientAssertion(
const client = await getClient(ctx, opts, clientId);
if (!client) {
throw new APIError("BAD_REQUEST", {
error_description: "unknown client",
error_description: "client not found",
error: "invalid_client",
});
}