From c5e0b6ad94f2dbeb3a77426f0f1d5a39b495a30c Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 14 Apr 2026 17:53:01 -0700 Subject: [PATCH 1/4] docs: add accounts portal OAuth consent refactor spec --- ...-accounts-oauth-consent-refactor-design.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md diff --git a/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md b/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md new file mode 100644 index 00000000000..212aab20054 --- /dev/null +++ b/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md @@ -0,0 +1,88 @@ +# Accounts Portal OAuth Consent Refactor Design + +> **For agentic workers:** This spec targets the accounts repo at `/Users/wobsoriano/Documents/projects/clerk/accounts`, not the javascript repo. + +**Goal:** Replace the accounts portal's manual OAuth consent implementation with the new `` component from `@clerk/nextjs/internal`, deleting all custom fetch utilities, hidden forms, and types in the process. + +**Context:** The `OAuthConsent` component (in `packages/ui`) now handles the full public path: it reads `client_id`, `redirect_uri`, and `scope` from the URL, fetches consent info via `clerk.oauthApplication.getConsentInfo`, renders scopes, and submits the consent form to `clerk.oauthApplication.buildConsentActionUrl`. The accounts portal's manual implementation duplicates all of this and can be deleted entirely. + +--- + +## Files Deleted + +- `components/oauth-consent/index.tsx` — manual fetch + `__internal_mountOAuthConsent` + hidden forms +- `utils/oauth-consent.ts` — `getConsentInfoForOAuth` FAPI fetch utility +- `types/OAuthConsent.ts` — `OAuthConsentInfo` type (only used by the above two files) + +## Files Modified + +### `types/index.ts` + +Remove the re-export of the deleted type file: + +```diff +- export * from './OAuthConsent'; + export * from './AccountPortalJSON'; + export * from './constants'; +``` + +`constants.ts` and `AccountPortalJSON.ts` are untouched — `DEV_BROWSER_JWT_MARKER` and `CLIENT_COOKIE_NAME` are still used elsewhere. + +### `pages/oauth-consent/[[...index]].tsx` + +Replace the entire file. `getServerSideProps` is removed — clerk-js handles `devBrowserJWT` and session auth automatically, and the new component reads all params from `window.location.search`. The referrer meta tag is kept (FAPI requires the `Origin` header on consent form POSTs). + +```tsx +import React from 'react'; +import Head from 'next/head'; +import { OAuthConsent } from '@clerk/nextjs/internal'; + +export default function ConsentPage(): JSX.Element { + return ( +
+
+ + + + +
+
+ ); +} +``` + +### `e2e/features/oauth-consent.test.ts` + +Error message text changes to match the new component's wording. Happy path assertion changes from hidden inputs (old hidden forms) to the Allow/Deny buttons the new component renders. + +| Old assertion | New assertion | +| --------------------------------------------------------- | ----------------------------------------- | +| `'Error: Authorization failed: The client ID is missing'` | `'The client ID is missing.'` | +| `'Error: Redirect URI not found'` | `'The redirect URI is missing.'` | +| `input[name="consented"][value="true"]` | `button[name="consented"][value="true"]` | +| `input[name="consented"][value="false"]` | `button[name="consented"][value="false"]` | + +### `e2e/unauthenticated/oauth-consent.test.ts` + +The old component returned an explicit `"Error: No session found"` div. The new component is wrapped with `withCoreUserGuard` which renders `null` for unauthenticated users. Update both tests to assert that the Allow button is not visible instead. + +```ts +// Before +await expect(page.getByText('Error: No session found')).toBeVisible(); + +// After +await expect(page.getByRole('button', { name: 'Allow' })).not.toBeVisible(); +``` + +--- + +## What Is Not Changing + +- `types/constants.ts` — stays, used by `utils/devBrowser.ts`, `utils/settings/environment.ts`, `utils/settings/accountPortal.ts` +- `utils/devBrowser.ts` — stays, unrelated to OAuth consent +- The page URL (`/oauth-consent`) and its Next.js route — unchanged +- The referrer meta tag — kept +- CSS class names (`pageContainer`, `componentContainer`) — unchanged From 28dfbef2fae7fe68ba31bfa2d2a500da81cba33c Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 16 Apr 2026 09:17:35 -0700 Subject: [PATCH 2/4] chore(clerk-js,shared): Add development runtime error for OAuthConsent when session is missing --- packages/clerk-js/src/core/clerk.ts | 9 +++++++++ packages/shared/src/internal/clerk-js/warnings.ts | 2 ++ 2 files changed, 11 insertions(+) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 4663e15eafe..fe4da6a8e91 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1336,6 +1336,15 @@ export class Clerk implements ClerkInterface { }; public __internal_mountOAuthConsent = (node: HTMLDivElement, props?: __internal_OAuthConsentProps) => { + if (noUserExists(this)) { + if (this.#instanceType === 'development') { + throw new ClerkRuntimeError(warnings.cannotRenderOAuthConsentComponentWhenUserDoesNotExist, { + code: CANNOT_RENDER_USER_MISSING_ERROR_CODE, + }); + } + return; + } + this.assertComponentsReady(this.#clerkUI); const component = 'OAuthConsent'; void this.#clerkUI diff --git a/packages/shared/src/internal/clerk-js/warnings.ts b/packages/shared/src/internal/clerk-js/warnings.ts index 2ec160247d8..2686713eaf7 100644 --- a/packages/shared/src/internal/clerk-js/warnings.ts +++ b/packages/shared/src/internal/clerk-js/warnings.ts @@ -62,6 +62,8 @@ const warnings = { 'The component cannot be rendered when user API keys are disabled. Since user API keys are disabled, this is no-op.', cannotRenderAPIKeysComponentForOrgWhenDisabled: 'The component cannot be rendered when organization API keys are disabled. Since organization API keys are disabled, this is no-op.', + cannotRenderOAuthConsentComponentWhenUserDoesNotExist: + ' cannot render unless a user is signed in. Since no user is signed in, this is no-op.', }; type SerializableWarnings = Serializable; From 686d357dbd4347b90b9ffc3e3963cb7c6fb58377 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 16 Apr 2026 09:18:29 -0700 Subject: [PATCH 3/4] chore: remove doc --- ...-accounts-oauth-consent-refactor-design.md | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md diff --git a/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md b/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md deleted file mode 100644 index 212aab20054..00000000000 --- a/docs/superpowers/specs/2026-04-14-accounts-oauth-consent-refactor-design.md +++ /dev/null @@ -1,88 +0,0 @@ -# Accounts Portal OAuth Consent Refactor Design - -> **For agentic workers:** This spec targets the accounts repo at `/Users/wobsoriano/Documents/projects/clerk/accounts`, not the javascript repo. - -**Goal:** Replace the accounts portal's manual OAuth consent implementation with the new `` component from `@clerk/nextjs/internal`, deleting all custom fetch utilities, hidden forms, and types in the process. - -**Context:** The `OAuthConsent` component (in `packages/ui`) now handles the full public path: it reads `client_id`, `redirect_uri`, and `scope` from the URL, fetches consent info via `clerk.oauthApplication.getConsentInfo`, renders scopes, and submits the consent form to `clerk.oauthApplication.buildConsentActionUrl`. The accounts portal's manual implementation duplicates all of this and can be deleted entirely. - ---- - -## Files Deleted - -- `components/oauth-consent/index.tsx` — manual fetch + `__internal_mountOAuthConsent` + hidden forms -- `utils/oauth-consent.ts` — `getConsentInfoForOAuth` FAPI fetch utility -- `types/OAuthConsent.ts` — `OAuthConsentInfo` type (only used by the above two files) - -## Files Modified - -### `types/index.ts` - -Remove the re-export of the deleted type file: - -```diff -- export * from './OAuthConsent'; - export * from './AccountPortalJSON'; - export * from './constants'; -``` - -`constants.ts` and `AccountPortalJSON.ts` are untouched — `DEV_BROWSER_JWT_MARKER` and `CLIENT_COOKIE_NAME` are still used elsewhere. - -### `pages/oauth-consent/[[...index]].tsx` - -Replace the entire file. `getServerSideProps` is removed — clerk-js handles `devBrowserJWT` and session auth automatically, and the new component reads all params from `window.location.search`. The referrer meta tag is kept (FAPI requires the `Origin` header on consent form POSTs). - -```tsx -import React from 'react'; -import Head from 'next/head'; -import { OAuthConsent } from '@clerk/nextjs/internal'; - -export default function ConsentPage(): JSX.Element { - return ( -
-
- - - - -
-
- ); -} -``` - -### `e2e/features/oauth-consent.test.ts` - -Error message text changes to match the new component's wording. Happy path assertion changes from hidden inputs (old hidden forms) to the Allow/Deny buttons the new component renders. - -| Old assertion | New assertion | -| --------------------------------------------------------- | ----------------------------------------- | -| `'Error: Authorization failed: The client ID is missing'` | `'The client ID is missing.'` | -| `'Error: Redirect URI not found'` | `'The redirect URI is missing.'` | -| `input[name="consented"][value="true"]` | `button[name="consented"][value="true"]` | -| `input[name="consented"][value="false"]` | `button[name="consented"][value="false"]` | - -### `e2e/unauthenticated/oauth-consent.test.ts` - -The old component returned an explicit `"Error: No session found"` div. The new component is wrapped with `withCoreUserGuard` which renders `null` for unauthenticated users. Update both tests to assert that the Allow button is not visible instead. - -```ts -// Before -await expect(page.getByText('Error: No session found')).toBeVisible(); - -// After -await expect(page.getByRole('button', { name: 'Allow' })).not.toBeVisible(); -``` - ---- - -## What Is Not Changing - -- `types/constants.ts` — stays, used by `utils/devBrowser.ts`, `utils/settings/environment.ts`, `utils/settings/accountPortal.ts` -- `utils/devBrowser.ts` — stays, unrelated to OAuth consent -- The page URL (`/oauth-consent`) and its Next.js route — unchanged -- The referrer meta tag — kept -- CSS class names (`pageContainer`, `componentContainer`) — unchanged From 8ba7a1095a07591fc1345527ef8e9f30633b0543 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 16 Apr 2026 09:20:33 -0700 Subject: [PATCH 4/4] chore: add changeset --- .changeset/happy-drinks-ring.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/happy-drinks-ring.md diff --git a/.changeset/happy-drinks-ring.md b/.changeset/happy-drinks-ring.md new file mode 100644 index 00000000000..51ba88172ed --- /dev/null +++ b/.changeset/happy-drinks-ring.md @@ -0,0 +1,6 @@ +--- +"@clerk/clerk-js": patch +"@clerk/shared": patch +--- + +Added development runtime error when mounting `` without active session.