Satellite domains cannot be tested when using ports on domains
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1.8k
- Forks
- 472
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 189
Description
Preliminary Checks
-
I have reviewed the documentation: https://clerk.com/docs
-
I have searched for existing issues: https://github.com/clerk/javascript/issues
-
I have not already reached out to Clerk support via email or Discord (if you have, no need to open an issue here)
-
This issue is not a question, general help request, or anything other than a bug report directly related to Clerk. Please ask questions in our Discord community: https://clerk.com/discord.
Reproduction
https://github.com/jeffreytgilbert/javascript/tree/fix/allowed-redirect-origins-non-default-ports
Publishable key
pk_test_aW50aW1hdGUtdXJjaGluLTc1LmNsZXJrLmFjY291bnRzLmRldiQ
Description
Summary
When using Clerk with custom local dev domains (e.g. Vite dev servers on :5173, :5174, :5176), the Account Portal ignores the redirect_url parameter and falls back to the configured Home URL. The root cause is in isAllowedRedirect in @clerk/shared: glob patterns like https://*.example.com never match origins that include a non-default port (https://sub.example.com:5176), so the check always fails and the warning fires.
Affected packages
@clerk/shared—isAllowedRedirectinsrc/internal/clerk-js/url.ts
Steps to reproduce
- Set up any Clerk satellite app running on a custom local domain with a non-default port — e.g.
https://app.admin.example.net:5174(a typical Vite dev server). - Use an auth passthrough pattern where the Account Portal's
redirect_urlpoints to another custom-domain dev server with a port — e.g.https://app.auth.example.net:5176/?target=.... - Sign in on the Account Portal (
intimate-urchin-75.accounts.devor equivalent dev instance). - Observe that the browser does not redirect to
https://app.auth.example.net:5176/?target=...— it goes to the Home URL configured in the Clerk Dashboard instead.
The browser console on the Account Portal shows:
Clerk: Redirect URL https://app.auth.example.net:5176/?target=... is not on one of the
allowedRedirectOrigins, falling back to the default redirect URL.
This occurs even though https://*.example.net is one of the origins in the generated allowedRedirectOrigins list (via createAllowedRedirectOrigins), and even if the user explicitly adds https://app.auth.example.net:5176 to the allowedRedirectOrigins prop on their ClerkProvider.
Expected behavior
https://*.example.net should match https://app.auth.example.net:5176. A pattern that covers a domain should cover that domain on any port, since non-default ports only appear in local dev environments — in production, HTTPS traffic is always on port 443 and url.port is the empty string.
Actual behavior
The match always fails for any URL with a non-default port when the pattern is a string. The warning fires and Clerk redirects to the Home URL, breaking the redirect_url flow entirely.
Root cause
isAllowedRedirect converts string entries in allowedRedirectOrigins to regexes using glob-to-regexp, then tests them against url.origin:
// packages/shared/src/internal/clerk-js/url.ts
const isAllowed =
!isProblematicUrl(url) &&
(isSameOrigin ||
allowedRedirectOrigins
.map(origin => (typeof origin === 'string' ? globs.toRegexp(trimTrailingSlash(origin)) : origin))
.some(origin => origin.test(trimTrailingSlash(url.origin))));
glob-to-regexp converts https://*.example.net to a regex roughly equivalent to /^https:\/\/[^/]*\.example\.net$/. When url.origin is https://app.auth.example.net:5176, the trailing :5176 causes the match to fail — the regex is anchored to .net$ and the port breaks it.
This also means that explicitly adding 'https://app.auth.example.net:5176' to allowedRedirectOrigins in the satellite's ClerkProvider does not help, because that prop controls validation within the satellite app's own SDK instance — it has no effect on the Account Portal's isAllowedRedirect check, which uses the origins generated by createAllowedRedirectOrigins (derived from the instance's FAPI configuration).
The default origins generated by createAllowedRedirectOrigins for a dev instance are:
https://jurying.net ← no port
https://*.jurying.net ← no port, glob fails on :5176
https://foo-bar-42.clerk.accounts.dev
None of these match a URL with a non-default port.
Proposed fix
After the full-origin test fails, also test each pattern against the port-stripped origin (protocol + hostname only) when the redirect URL has an explicit non-default port. url.port is an empty string for default ports (443 for https, 80 for http), so this fallback is only reached in non-standard-port contexts. The domain must still satisfy the pattern — only the port check is relaxed.
const patterns = allowedRedirectOrigins.map(origin =>
typeof origin === 'string' ? globs.toRegexp(trimTrailingSlash(origin)) : origin,
);
const portlessOrigin = url.port ? `${url.protocol}//${url.hostname}` : null;
const isAllowed =
!isProblematicUrl(url) &&
(isSameOrigin ||
patterns.some(
pattern =>
pattern.test(trimTrailingSlash(url.origin)) ||
(portlessOrigin !== null && pattern.test(portlessOrigin)),
));
A PR with this fix, tests, and a changeset is available at: https://github.com/jeffreytgilbert/javascript/tree/fix/allowed-redirect-origins-non-default-ports
Why the ClerkProvider.allowedRedirectOrigins prop doesn't help
Worth calling out explicitly because it's the obvious first thing people try. The allowedRedirectOrigins prop on a satellite's ClerkProvider only gates redirects within that satellite app's own SDK context. The Account Portal is Clerk's hosted application — you cannot pass props to its ClerkProvider. Its allowedRedirectOrigins list comes from createAllowedRedirectOrigins, which uses the Clerk instance's FAPI-configured origins. No amount of configuring the satellite's ClerkProvider affects what the Account Portal validates.
Environment
@clerk/shared: 4.4.0 (confirmed in source — behaviour is present in currentmain)- Trigger context: Account Portal → satellite redirect in local dev with Vite (non-standard port)
- Reproduces with both the default origins from
createAllowedRedirectOriginsand user-supplied string entries inallowedRedirectOrigins
Environment
System:
OS: macOS 26.4
CPU: (10) arm64 Apple M1 Max
Memory: 2.87 GB / 64.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.14.1 - /Users/jeffreygilbert/.nvm/versions/node/v24.14.1/bin/node
npm: 11.11.0 - /Users/jeffreygilbert/.nvm/versions/node/v24.14.1/bin/npm
pnpm: 10.33.0 - /opt/homebrew/bin/pnpm
bun: 1.3.11 - /opt/homebrew/bin/bun
Browsers:
Brave Browser: 146.1.88.136
Chrome: 146.0.7680.178
Firefox: 148.0.2
Safari: 26.4
Safari Technology Preview: 26.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/shared/src/internal/clerk-js/url.ts at isAllowedRedirect and review the reported reproduction for satellite redirects using non-default ports. Run or add focused tests covering wildcard origins and explicit ports, then verify that valid non-default-port redirects are accepted without weakening domain matching; the issue also mentions a changeset and an existing PR branch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100