Preliminary Checks
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 — isAllowedRedirect in src/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_url points 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.dev or 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 current main)
- Trigger context: Account Portal → satellite redirect in local dev with Vite (non-standard port)
- Reproduces with both the default origins from
createAllowedRedirectOrigins and user-supplied string entries in allowedRedirectOrigins
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
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 theredirect_urlparameter and falls back to the configured Home URL. The root cause is inisAllowedRedirectin@clerk/shared: glob patterns likehttps://*.example.comnever 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.tsSteps to reproduce
https://app.admin.example.net:5174(a typical Vite dev server).redirect_urlpoints to another custom-domain dev server with a port — e.g.https://app.auth.example.net:5176/?target=....intimate-urchin-75.accounts.devor equivalent dev instance).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:
This occurs even though
https://*.example.netis one of the origins in the generatedallowedRedirectOriginslist (viacreateAllowedRedirectOrigins), and even if the user explicitly addshttps://app.auth.example.net:5176to theallowedRedirectOriginsprop on theirClerkProvider.Expected behavior
https://*.example.netshould matchhttps://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 andurl.portis 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_urlflow entirely.Root cause
isAllowedRedirectconverts string entries inallowedRedirectOriginsto regexes usingglob-to-regexp, then tests them againsturl.origin:glob-to-regexpconvertshttps://*.example.netto a regex roughly equivalent to/^https:\/\/[^/]*\.example\.net$/. Whenurl.originishttps://app.auth.example.net:5176, the trailing:5176causes 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'toallowedRedirectOriginsin the satellite'sClerkProviderdoes not help, because that prop controls validation within the satellite app's own SDK instance — it has no effect on the Account Portal'sisAllowedRedirectcheck, which uses the origins generated bycreateAllowedRedirectOrigins(derived from the instance's FAPI configuration).The default origins generated by
createAllowedRedirectOriginsfor a dev instance are: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 + hostnameonly) when the redirect URL has an explicit non-default port.url.portis 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.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.allowedRedirectOriginsprop doesn't helpWorth calling out explicitly because it's the obvious first thing people try. The
allowedRedirectOriginsprop on a satellite'sClerkProvideronly gates redirects within that satellite app's own SDK context. The Account Portal is Clerk's hosted application — you cannot pass props to itsClerkProvider. ItsallowedRedirectOriginslist comes fromcreateAllowedRedirectOrigins, which uses the Clerk instance's FAPI-configured origins. No amount of configuring the satellite'sClerkProvideraffects what the Account Portal validates.Environment
@clerk/shared: 4.4.0 (confirmed in source — behaviour is present in currentmain)createAllowedRedirectOriginsand user-supplied string entries inallowedRedirectOriginsEnvironment
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