Skip to content

clerk-js: "capacitor:" missing from the navigate() protocol allow-list, breaking Clerk in Capacitor iOS apps #9336

Description

@mehru9219

Summary

Clerk.navigate() validates the protocol of every internal navigation against a hardcoded allow-list that includes wails: and chrome-extension: but not capacitor:. As a result, Clerk is unusable in a Capacitor iOS app: every relative navigation is rewritten to /, which breaks sign-in.

The code

From the shipped clerk.browser.js v5 (module 55809):

let r = ["http:", "https:", "wails:", "chrome-extension:"];

and in Clerk.navigate:

let i = new URL(e, window.location.href);
this.#N.includes(i.protocol) || (
  console.warn(`Clerk: "${i.protocol}" is not a valid protocol. Redirecting to "/" instead. ...`),
  i = new URL("/", window.location.href)
);

Why this breaks Capacitor specifically

Capacitor's iOS WebView serves bundled content from a custom scheme — capacitor://localhost by default. iosScheme cannot be set to http/https, because WKWebView reserves them (Capacitor docs, and Apple's setURLSchemeHandler documentation).

Because the target is resolved with new URL(e, window.location.href), every relative navigation on that origin returns protocol capacitor:, fails the check, and is rewritten to /.

Observed on device:

⚡️ [warn] - Clerk: "capacitor:" is not a valid protocol. Redirecting to "/" instead.

fired twice — once during initialisation and once mid sign-in. User-visible symptom: the email verification screen appears and is immediately replaced by the sign-up route, so sign-in can never complete.

Not fixable by configuration

signInUrl, signUpUrl, forceRedirectUrl, fallbackRedirectUrl etc. all supply relative paths, which is exactly what resolves to the rejected protocol. Supplying absolute https:// URLs passes the check but navigates the WebView out of the app, which is worse.

Proposed fix

Add "capacitor:" to the list. Clerk's docs already name Capacitor.js as a supported browser-like stack for allowed_origins, and wails: / chrome-extension: establish the precedent for embedded-shell schemes.

- let r = ["http:", "https:", "wails:", "chrome-extension:"];
+ let r = ["http:", "https:", "wails:", "chrome-extension:", "capacitor:"];

Ideally the list would also be extensible via ClerkOptions, so custom iosScheme values (Capacitor allows any non-reserved scheme) are supported without another release.

Current workaround

Setting iosScheme: 'wails' in capacitor.config.ts, so the origin becomes wails://localhost and satisfies the existing list. It works, but borrowing another framework's scheme name is obviously not intended usage.

Environment

  • @clerk/clerk-react 5.35.3, @clerk/types 4.68.0, clerk-js v5 (hot-loaded)
  • Capacitor 8, iOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions