From 74cdffdddeba8b08e4455d86c2062913d6175760 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 28 Jul 2026 19:36:09 -0400 Subject: [PATCH] refactor(oauth-provider)!: replace client `type` with `application_type` Client registration carried two overlapping profile fields. `type` (web/native/user-agent-based) was a better-auth invention with no RFC 7591 standing, validated only for self-consistency against the auth method; `application_type` is the OIDC Registration field, and the one MCP clients are told to send. Registration accepted both without cross-checking, so a client could declare `type: "web"` alongside `application_type: "native"` and be stored claiming two profiles. Collapsing to `application_type` removes the ambiguity and gives the field a real column rather than the untyped metadata bag. `user-agent-based` has no successor: a browser app registers with `token_endpoint_auth_method: "none"`, which already marks it public. PKCE enforcement reads `application_type` for the native case that `type` used to cover, so a native client stays public whatever auth method it registered with. --- .changeset/oauth-client-application-type.md | 10 ++ .../docs/plugins/device-authorization.mdx | 2 +- docs/content/docs/plugins/oauth-provider.mdx | 6 +- packages/cimd/src/client-store.ts | 1 - .../oauth-provider/src/device-code.test.ts | 4 - .../oauth-provider/src/extensions.test.ts | 12 --- packages/oauth-provider/src/oauth.ts | 8 +- .../oauth-provider/src/oauthClient/index.ts | 26 +++--- packages/oauth-provider/src/register.test.ts | 93 ++++++++----------- packages/oauth-provider/src/register.ts | 42 +-------- packages/oauth-provider/src/schema.ts | 2 +- packages/oauth-provider/src/types/index.ts | 11 +-- packages/oauth-provider/src/types/oauth.ts | 1 - packages/oauth-provider/src/types/zod.ts | 4 +- packages/oauth-provider/src/utils/index.ts | 3 +- 15 files changed, 82 insertions(+), 143 deletions(-) create mode 100644 .changeset/oauth-client-application-type.md diff --git a/.changeset/oauth-client-application-type.md b/.changeset/oauth-client-application-type.md new file mode 100644 index 0000000000..2a3fd3f39d --- /dev/null +++ b/.changeset/oauth-client-application-type.md @@ -0,0 +1,10 @@ +--- +"@better-auth/oauth-provider": minor +"@better-auth/cimd": minor +--- + +OAuth clients describe their profile with one field, `application_type`, instead of two overlapping ones. The non-standard `type` field is removed, and its `web` and `native` values move to `application_type`; `user-agent-based` has no replacement, since a browser app registers with `token_endpoint_auth_method: "none"`, which already marks it public. + +This release is breaking. Registration requests and the client management APIs no longer accept `type`, and client responses no longer return it. Replace `type: "web"` with `application_type: "web"`, `type: "native"` with `application_type: "native"`, and drop `type: "user-agent-based"`. A `native` client is now always treated as public, so PKCE is required for it even when the client registered with `require_pkce: false`. + +The client table gains an `applicationType` column and no longer uses `type`. Run a migration after upgrading with `npx auth migrate` or `npx auth generate`. Existing clients keep working; those that had set `type` are treated as if no application type was declared, which leaves their redirect URIs unconstrained. diff --git a/docs/content/docs/plugins/device-authorization.mdx b/docs/content/docs/plugins/device-authorization.mdx index adc554e83b..f5a5321c39 100644 --- a/docs/content/docs/plugins/device-authorization.mdx +++ b/docs/content/docs/plugins/device-authorization.mdx @@ -147,7 +147,7 @@ await auth.api.adminCreateOAuthClient({ headers, body: { token_endpoint_auth_method: "none", - type: "native", + application_type: "native", grant_types: [DEVICE_CODE_GRANT_TYPE, "refresh_token"], scope: "openid profile offline_access api:read", resources: ["https://api.example.com"], diff --git a/docs/content/docs/plugins/oauth-provider.mdx b/docs/content/docs/plugins/oauth-provider.mdx index 20d8f692d8..167725cd98 100644 --- a/docs/content/docs/plugins/oauth-provider.mdx +++ b/docs/content/docs/plugins/oauth-provider.mdx @@ -448,7 +448,7 @@ const client = await authClient.oauth2.register({ For all endpoint parameters, see [RFC 7591 Registration](https://datatracker.ietf.org/doc/html/rfc7591#section-2). -When `application_type` ([OIDC Registration §2](https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata)) is provided, redirect URIs are constrained to what the declared type allows. A `native` client may use custom schemes, loopback `http` redirects, or claimed `https` links ([RFC 8252 §7](https://datatracker.ietf.org/doc/html/rfc8252#section-7)); plain `http` on a routable host is rejected. A `web` client requires `https` redirect URIs on non-loopback hosts. Sending both `application_type` and `type` requires them to agree. When `application_type` is omitted, no constraints are applied. +When `application_type` ([OIDC Registration §2](https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata)) is provided, redirect URIs are constrained to what the declared type allows. A `native` client may use custom schemes, loopback `http` redirects, or claimed `https` links ([RFC 8252 §7](https://datatracker.ietf.org/doc/html/rfc8252#section-7)); plain `http` on a routable host is rejected. A `web` client requires `https` redirect URIs on non-loopback hosts, and is the only type that may authenticate at the token endpoint with a secret; a `native` client is always treated as public, so PKCE is required for it. When `application_type` is omitted, no constraints are applied. The [MCP 2026-07-28 spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#application-type-and-redirect-uri-constraints) requires MCP clients to send an appropriate `application_type`. The same constraints apply to clients identified by a [Client ID Metadata Document](/docs/plugins/cimd). @@ -2005,10 +2005,10 @@ export const oauthClientTableFields = [ isOptional: true, }, { - name: "type", + name: "application_type", type: "string", description: - "Type of OAuth client. Supports: ['web', 'native', 'user-agent-based']", + "OIDC Registration application type. Supports: ['web', 'native']. Native clients are always treated as public.", isOptional: true, }, { diff --git a/packages/cimd/src/client-store.ts b/packages/cimd/src/client-store.ts index db34edda2e..99c007d539 100644 --- a/packages/cimd/src/client-store.ts +++ b/packages/cimd/src/client-store.ts @@ -99,7 +99,6 @@ const ALLOWED_METADATA_FIELDS = new Set([ "post_logout_redirect_uris", "subject_type", "application_type", - "type", "jwks", "jwks_uri", ]); diff --git a/packages/oauth-provider/src/device-code.test.ts b/packages/oauth-provider/src/device-code.test.ts index e03a84e44a..b54718d2ec 100644 --- a/packages/oauth-provider/src/device-code.test.ts +++ b/packages/oauth-provider/src/device-code.test.ts @@ -61,7 +61,6 @@ describe("oauth-provider device-code grant", async () => { token_endpoint_auth_method: "none", grant_types: grantTypes, scope: "openid profile email", - type: "native", }, }); return created!.client_id; @@ -353,7 +352,6 @@ describe("oauth-provider device-code grant", async () => { token_endpoint_auth_method: "none", grant_types: [DEVICE_CODE_GRANT_TYPE], scope: "openid", - type: "native", }, }); @@ -380,7 +378,6 @@ describe("oauth-provider device-code grant", async () => { token_endpoint_auth_method: "client_secret_basic", grant_types: [DEVICE_CODE_GRANT_TYPE], scope: "openid", - type: "web", }, }); @@ -480,7 +477,6 @@ describe("oauth-provider device-code grant expiry", async () => { token_endpoint_auth_method: "none", grant_types: [DEVICE_CODE_GRANT_TYPE], scope: "openid", - type: "native", }, }); const clientId = created!.client_id; diff --git a/packages/oauth-provider/src/extensions.test.ts b/packages/oauth-provider/src/extensions.test.ts index c096bd5ac6..a1d7aea0a4 100644 --- a/packages/oauth-provider/src/extensions.test.ts +++ b/packages/oauth-provider/src/extensions.test.ts @@ -273,7 +273,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionGrant], scope: "openid email vc", - type: "web", }, }); expect(adminClient?.client_id).toBeDefined(); @@ -289,7 +288,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionGrant], scope: "openid email vc", - type: "web", }, }); expect(registeredClient?.client_id).toBeDefined(); @@ -311,7 +309,6 @@ describe("oauth-provider extensions", async () => { grant_types: [extensionGrant], response_types: ["code"], scope: "openid email vc", - type: "web", }, }); } catch (e) { @@ -336,7 +333,6 @@ describe("oauth-provider extensions", async () => { grant_types: [extensionGrant], jwks_uri: "https://127.0.0.1/jwks", scope: "openid email vc", - type: "web", }, }); } catch (e) { @@ -362,7 +358,6 @@ describe("oauth-provider extensions", async () => { jwks: [{ kty: "RSA", n: "test", e: "test-exponent" }], jwks_uri: "https://client.example.com/jwks", scope: "openid email vc", - type: "web", }, }); } catch (e) { @@ -397,7 +392,6 @@ describe("oauth-provider extensions", async () => { body: { grant_types: [extensionGrant], scope: "openid email vc", - type: "web", }, }); const response = await client.$fetch("/oauth2/token", { @@ -496,7 +490,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionGrant], scope: "openid email vc", - type: "web", }, }); expect(oauthClient?.client_id).toBeDefined(); @@ -608,7 +601,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionOpaqueGrant], scope: "openid email vc", - type: "web", }, }); const response = await client.$fetch("/oauth2/token", { @@ -975,7 +967,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionOpaqueGrant], scope: "openid email vc", - type: "web", }, }); const tokenResponse = await client.$fetch<{ access_token: string }>( @@ -1025,7 +1016,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionGrant], scope: "openid email vc", - type: "web", }, }); const response = await client.$fetch("/oauth2/token", { @@ -1052,7 +1042,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionOpaqueGrant], scope: "openid email vc", - type: "web", }, }); const tokenResponse = await client.$fetch<{ access_token: string }>( @@ -1134,7 +1123,6 @@ describe("oauth-provider extensions", async () => { token_endpoint_auth_method: extensionAuthMethod, grant_types: [extensionOpaqueBoundGrant], scope: "openid email vc", - type: "web", }, }); const clientAuth = { diff --git a/packages/oauth-provider/src/oauth.ts b/packages/oauth-provider/src/oauth.ts index ec90b9b2e3..842b510e34 100644 --- a/packages/oauth-provider/src/oauth.ts +++ b/packages/oauth-provider/src/oauth.ts @@ -1598,12 +1598,12 @@ export const oauthProvider = >(options: O) => { public: { type: "boolean", description: - "Whether the client is public as determined by the type", + "Whether the client is a public client", }, - type: { + application_type: { type: "string", - description: "Type of the client", - enum: ["web", "native", "user-agent-based"], + description: "OIDC Registration application type", + enum: ["web", "native"], }, disabled: { type: "boolean", diff --git a/packages/oauth-provider/src/oauthClient/index.ts b/packages/oauth-provider/src/oauthClient/index.ts index 1931efdc5b..428877a896 100644 --- a/packages/oauth-provider/src/oauthClient/index.ts +++ b/packages/oauth-provider/src/oauthClient/index.ts @@ -48,7 +48,7 @@ export const adminCreateOAuthClient = (opts: OAuthOptions) => jwks_uri: z.string().optional(), grant_types: grantTypesSchema.optional(), response_types: z.array(z.enum(["code"])).optional(), - type: z.enum(["web", "native", "user-agent-based"]).optional(), + application_type: z.enum(["web", "native"]).optional(), // SERVER_ONLY applicable fields client_secret_expires_at: z .union([z.string(), z.number()]) @@ -177,13 +177,12 @@ export const adminCreateOAuthClient = (opts: OAuthOptions) => }, public: { type: "boolean", - description: - "Whether the client is public as determined by the type", + description: "Whether the client is a public client", }, - type: { + application_type: { type: "string", - description: "Type of the client", - enum: ["web", "native", "user-agent-based"], + description: "OIDC Registration application type", + enum: ["web", "native"], }, disabled: { type: "boolean", @@ -250,7 +249,7 @@ export const createOAuthClient = (opts: OAuthOptions) => jwks_uri: z.string().optional(), grant_types: grantTypesSchema.optional(), response_types: z.array(z.enum(["code"])).optional(), - type: z.enum(["web", "native", "user-agent-based"]).optional(), + application_type: z.enum(["web", "native"]).optional(), // RFC 9449 §5.2: client asks for DPoP-bound access tokens. dpop_bound_access_tokens: z.boolean().optional(), }), @@ -368,13 +367,12 @@ export const createOAuthClient = (opts: OAuthOptions) => }, public: { type: "boolean", - description: - "Whether the client is public as determined by the type", + description: "Whether the client is a public client", }, - type: { + application_type: { type: "string", - description: "Type of the client", - enum: ["web", "native", "user-agent-based"], + description: "OIDC Registration application type", + enum: ["web", "native"], }, disabled: { type: "boolean", @@ -509,7 +507,7 @@ export const adminUpdateOAuthClient = (opts: OAuthOptions) => // NOTE: token_endpoint_auth_method is currently immutable since it changes isPublic definition grant_types: grantTypesSchema.optional(), response_types: z.array(z.enum(["code"])).optional(), - type: z.enum(["web", "native", "user-agent-based"]).optional(), + application_type: z.enum(["web", "native"]).optional(), // SERVER_ONLY applicable fields client_secret_expires_at: z .union([z.string(), z.number()]) @@ -559,7 +557,7 @@ export const updateOAuthClient = (opts: OAuthOptions) => // NOTE: token_endpoint_auth_method is currently immutable since it changes isPublic definition grant_types: grantTypesSchema.optional(), response_types: z.array(z.enum(["code"])).optional(), - type: z.enum(["web", "native", "user-agent-based"]).optional(), + application_type: z.enum(["web", "native"]).optional(), }), }), metadata: { diff --git a/packages/oauth-provider/src/register.test.ts b/packages/oauth-provider/src/register.test.ts index 9e3de8319b..4c57651f8d 100644 --- a/packages/oauth-provider/src/register.test.ts +++ b/packages/oauth-provider/src/register.test.ts @@ -110,37 +110,12 @@ describe("oauth register", async () => { expect(response.error?.status).toBe(400); }); - it("should fail type check for public client request", async () => { - const response = await serverClient.oauth2.register({ - token_endpoint_auth_method: "none", - type: "web", - redirect_uris: [redirectUri], - }); - expect(response.error?.status).toBe(400); - }); - - it.for([ - "native", - "user-agent-based", - ] as OAuthClient["type"][])("should fail with type '%s' check for confidential client request", async (type) => { - const response = await serverClient.oauth2.register({ - token_endpoint_auth_method: "client_secret_post", - type, - redirect_uris: [redirectUri], - }); - expect(response.error?.status).toBe(400); - }); - - it.for([ - "native", - "user-agent-based", - ] as OAuthClient["type"][])("should register public '%s' client with minimum requirements via server", async (type) => { + it("should register public client with minimum requirements via server", async () => { const response = await auth.api.adminCreateOAuthClient({ headers, body: { token_endpoint_auth_method: "none", redirect_uris: [redirectUri], - type, }, }); expect(response?.client_id).toBeDefined(); @@ -182,7 +157,6 @@ describe("oauth register", async () => { response_types: ["code"], //---- RFC6749 Spec ----// public: true, // test never set on this (based off of token_endpoint_auth_method) - type: "web", //---- Not Part of RFC7591 Spec ----// disabled: false, }; @@ -234,10 +208,9 @@ describe("oauth register", async () => { expect(response.data?.disabled).toBeFalsy(); }); - it("should preserve confidential method and type for authenticated registration", async () => { + it("should preserve the confidential auth method for authenticated registration", async () => { const response = await serverClient.oauth2.register({ token_endpoint_auth_method: "client_secret_post", - type: "web", redirect_uris: [redirectUri], }); expect(response.data?.client_id).toBeDefined(); @@ -245,7 +218,6 @@ describe("oauth register", async () => { expect(response.data?.token_endpoint_auth_method).toBe( "client_secret_post", ); - expect(response.data?.type).toBe("web"); expect(response.data?.public).toBeFalsy(); }); @@ -480,7 +452,6 @@ describe("oauth register", async () => { const response = await serverClient.oauth2.register({ redirect_uris: [redirectUri], token_endpoint_auth_method: "none", - type: "native", backchannel_logout_uri: `${rpBaseUrl}/logout/backchannel`, }); expect(response.data?.client_id).toBeDefined(); @@ -645,10 +616,9 @@ describe("oauth register - unauthenticated", async () => { /** * @see https://github.com/better-auth/better-auth/issues/8588 */ - it("should preserve type 'web' for unauthenticated confidential DCR", async () => { + it("should keep unauthenticated confidential DCR confidential", async () => { const response = await unauthenticatedClient.oauth2.register({ token_endpoint_auth_method: "client_secret_post", - type: "web", redirect_uris: [redirectUri], }); expect(response.data?.client_id).toBeDefined(); @@ -656,7 +626,7 @@ describe("oauth register - unauthenticated", async () => { expect(response.data?.token_endpoint_auth_method).toBe( "client_secret_post", ); - expect(response.data?.type).toBe("web"); + expect(response.data?.public).toBeFalsy(); }); /** @@ -1427,7 +1397,7 @@ describe("oauth register - protected dynamic registration", async () => { describe("oauth register - application_type", async () => { const authServerBaseUrl = "http://localhost:3000"; - const { customFetchImpl } = await getTestInstance({ + const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({ baseURL: authServerBaseUrl, plugins: [ jwt(), @@ -1507,25 +1477,42 @@ describe("oauth register - application_type", async () => { expect(body.application_type).toBe("web"); }); - it("rejects an application_type that contradicts type", async () => { - const response = await register({ - type: "web", - application_type: "native", - redirect_uris: ["https://rp.example.com/callback"], - }); - expect(response.status).toBe(400); - const body = (await response.json()) as { error_description?: string }; - expect(body.error_description).toContain("conflicts with type"); - }); + it("forces PKCE on a native client even when the client opts out", async () => { + // A native client cannot keep a secret, so it is public whatever auth + // method it registered with, and `require_pkce: false` cannot waive PKCE + // for it. The same client declared `web` is taken at its word. + const { headers } = await signInWithTestUser(); + const authorizeWithoutPkce = async ( + applicationType: "web" | "native", + redirectUri: string, + ) => { + const created = await auth.api.adminCreateOAuthClient({ + headers, + body: { + application_type: applicationType, + token_endpoint_auth_method: "client_secret_post", + require_pkce: false, + redirect_uris: [redirectUri], + }, + }); + const response = await customFetchImpl( + `${authServerBaseUrl}/api/auth/oauth2/authorize?${new URLSearchParams({ + client_id: created!.client_id!, + response_type: "code", + redirect_uri: redirectUri, + scope: "openid", + })}`, + { method: "GET", redirect: "manual" }, + ); + return response.headers.get("location") ?? ""; + }; - it("accepts a user-agent-based client declaring the web application type", async () => { - const response = await register({ - type: "user-agent-based", - application_type: "web", - token_endpoint_auth_method: "none", - redirect_uris: ["https://spa.example.com/callback"], - }); - expect(response.status).toBe(201); + expect( + await authorizeWithoutPkce("native", "com.example.pkce:/cb"), + ).toContain("pkce+is+required"); + expect( + await authorizeWithoutPkce("web", "https://rp.example.com/pkce-cb"), + ).not.toContain("pkce+is+required"); }); it("keeps prior behavior when application_type is omitted", async () => { diff --git a/packages/oauth-provider/src/register.ts b/packages/oauth-provider/src/register.ts index 52ec6eb297..c13097d3be 100644 --- a/packages/oauth-provider/src/register.ts +++ b/packages/oauth-provider/src/register.ts @@ -258,27 +258,6 @@ export async function checkOAuthClient( }); } - // Check value of type, if sent, matches isPublic - if (clientWithDefaults.type) { - if ( - isPublic && - !( - clientWithDefaults.type === "native" || - clientWithDefaults.type === "user-agent-based" - ) - ) { - throw new APIError("BAD_REQUEST", { - error: "invalid_client_metadata", - error_description: `Type must be 'native' or 'user-agent-based' for public applications`, - }); - } else if (!isPublic && !(clientWithDefaults.type === "web")) { - throw new APIError("BAD_REQUEST", { - error: "invalid_client_metadata", - error_description: `Type must be 'web' for confidential applications`, - }); - } - } - const grantTypes = clientWithDefaults.grant_types ?? []; const responseTypes = clientWithDefaults.response_types; @@ -295,19 +274,6 @@ export async function checkOAuthClient( }); } - // `type` and `application_type` both describe the client profile, so a - // registration that sends both must not contradict itself. - if (clientWithDefaults.type && clientWithDefaults.application_type) { - const impliedApplicationType = - clientWithDefaults.type === "native" ? "native" : "web"; - if (impliedApplicationType !== clientWithDefaults.application_type) { - throw new APIError("BAD_REQUEST", { - error: "invalid_client_metadata", - error_description: `application_type '${clientWithDefaults.application_type}' conflicts with type '${clientWithDefaults.type}'`, - }); - } - } - // OIDC Registration §2 `application_type`, required of MCP clients by // SEP-837. Native redirect URIs follow RFC 8252 §7 (custom scheme, loopback // http, or claimed https link) rather than OIDC's narrower list, which @@ -765,7 +731,7 @@ export function oauthToSchema(input: OAuthClient): SchemaClient { response_types: responseTypes, // RFC6749 Spec public: _public, - type, + application_type: applicationType, // Not Part of RFC7591 Spec disabled, skip_consent: skipConsent, @@ -833,7 +799,7 @@ export function oauthToSchema(input: OAuthClient): SchemaClient { jwksUri: jwksUri, // RFC6749 Spec public: _public, - type, + applicationType, // All other metadata skipConsent, enableEndSession, @@ -884,7 +850,7 @@ export function schemaToOAuth(input: SchemaClient): OAuthClient { responseTypes, // RFC6749 Spec public: _public, - type, + applicationType, // Jwks jwks, jwksUri, @@ -946,7 +912,7 @@ export function schemaToOAuth(input: SchemaClient): OAuthClient { response_types: responseTypes ?? undefined, // RFC6749 Spec public: _public ?? undefined, - type: type ?? undefined, + application_type: applicationType ?? undefined, // Not Part of RFC7591 Spec disabled: disabled ?? undefined, skip_consent: skipConsent ?? undefined, diff --git a/packages/oauth-provider/src/schema.ts b/packages/oauth-provider/src/schema.ts index 5939b25c8e..b1d66a9cda 100644 --- a/packages/oauth-provider/src/schema.ts +++ b/packages/oauth-provider/src/schema.ts @@ -133,7 +133,7 @@ export const schema = { type: "boolean", required: false, }, - type: { + applicationType: { type: "string", required: false, }, diff --git a/packages/oauth-provider/src/types/index.ts b/packages/oauth-provider/src/types/index.ts index ca9f42e0cb..7863ad833d 100644 --- a/packages/oauth-provider/src/types/index.ts +++ b/packages/oauth-provider/src/types/index.ts @@ -1712,15 +1712,14 @@ export interface SchemaClient< */ public?: boolean; /** - * The client type + * OIDC Registration application type. * - * Described https://www.rfc-editor.org/rfc/rfc6749.html#section-2.1 + * Constrains which redirect URIs the client may register, and marks + * `native` clients as public so PKCE is always required for them. * - * - web - A web application (confidential client) - * - native - A mobile application (public client) - * - user-agent-based - A user-agent-based application (public client) + * @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata */ - type?: "web" | "native" | "user-agent-based"; + applicationType?: "web" | "native"; /** * Whether this client requires PKCE for authorization code flow. * diff --git a/packages/oauth-provider/src/types/oauth.ts b/packages/oauth-provider/src/types/oauth.ts index 3be6f56c0d..e3bd11e44e 100644 --- a/packages/oauth-provider/src/types/oauth.ts +++ b/packages/oauth-provider/src/types/oauth.ts @@ -399,7 +399,6 @@ export interface OAuthClient { application_type?: "web" | "native"; //---- RFC6749 Spec ----// public?: boolean; - type?: "web" | "native" | "user-agent-based"; //---- Not Part of RFC7591 Spec ----// disabled?: boolean; skip_consent?: boolean; diff --git a/packages/oauth-provider/src/types/zod.ts b/packages/oauth-provider/src/types/zod.ts index 78155bd8b4..3444a76740 100644 --- a/packages/oauth-provider/src/types/zod.ts +++ b/packages/oauth-provider/src/types/zod.ts @@ -206,9 +206,7 @@ export const clientRegistrationRequestSchema = z.object({ jwks_uri: z.string().optional(), grant_types: z.array(z.string().trim().min(1)).min(1).optional(), response_types: z.array(z.enum(["code"])).optional(), - type: z.enum(["web", "native", "user-agent-based"]).optional(), - // OIDC Registration §2: constrains redirect URI schemes and hosts when - // provided, and must agree with `type` when both are sent. + // OIDC Registration §2: constrains redirect URI schemes and hosts. application_type: z.enum(["web", "native"]).optional(), subject_type: z.enum(["public", "pairwise"]).optional(), // RFC 9449 §5.2: client asks for DPoP-bound access tokens. diff --git a/packages/oauth-provider/src/utils/index.ts b/packages/oauth-provider/src/utils/index.ts index a827725919..a7c207e45f 100644 --- a/packages/oauth-provider/src/utils/index.ts +++ b/packages/oauth-provider/src/utils/index.ts @@ -956,8 +956,7 @@ export function isPKCERequired( // Determine if client is public const isPublicClient = client.tokenEndpointAuthMethod === "none" || - client.type === "native" || - client.type === "user-agent-based" || + client.applicationType === "native" || client.public === true; // PKCE always required for public clients