Skip to content

Bug: startNewFlow uses absolute path "/self-service/...", breaks integrations mounted under a sub-path#597

Draft
hersentino wants to merge 1 commit intoory:mainfrom
hersentino:fix-startNewFlow-fonction
Draft

Bug: startNewFlow uses absolute path "/self-service/...", breaks integrations mounted under a sub-path#597
hersentino wants to merge 1 commit intoory:mainfrom
hersentino:fix-startNewFlow-fonction

Conversation

@hersentino
Copy link
Copy Markdown

@hersentino hersentino commented Apr 28, 2026

Preflight checklist

Ory Network Project

Self-hosted Ory Kratos (proxied through @ory/nextjs middleware).

Describe the bug

startNewFlow() in @ory/nextjs/app builds the redirect URL with a leading slash:

new URL(
  "/self-service/" + flowType + "/browser?" + urlQueryToSearchParams(params).toString(),
  baseUrl,
)

Because the first argument starts with /, the URL constructor treats it as an
absolute path and discards any path prefix carried by baseUrl.

baseUrl here comes from guessPotentiallyProxiedOrySdkUrl({ knownProxiedUrl: await getPublicUrl() }),
which returns the public origin of the Next.js app (the URL the browser sees).
For any deployment where the Next.js app is mounted under a sub-path
e.g. behind an ingress / reverse proxy that routes https://example.com/app/*
to the app, or when using Next.js basePath — the redirect drops the prefix
and points to https://example.com/self-service/... instead of
https://example.com/app/self-service/.... The middleware never sees the
request, the Kratos proxy never runs, and the user lands on a 404 (or worse,
an unrelated route on the parent domain).

This affects every flow that goes through getFlowFactorystartNewFlow:
login, registration, recovery, verification, settings.

The fix is to drop the leading / so the path is resolved relative to
baseUrl
, which is the documented contract of new URL(input, base):

new URL(
  "self-service/" + flowType + "/browser?" + urlQueryToSearchParams(params).toString(),
  baseUrl,
)

When baseUrl has no sub-path (the common case), the resulting URL is
identical, so this change is backwards-compatible for vanilla deployments
and fixes sub-path deployments.

Reproducing the bug

  1. Deploy a Next.js app using @ory/nextjs behind a reverse proxy that mounts
    it under a sub-path, e.g. https://example.com/app/. Make sure the proxy
    forwards Host / X-Forwarded-* headers so getPublicUrl() returns
    https://example.com/app/.
  2. Visit https://example.com/app/login without an existing flow ID.
  3. Observe the 30x redirect: Location: https://example.com/self-service/login/browser?...
    instead of https://example.com/app/self-service/login/browser?....
  4. The @ory/nextjs middleware (matched on /app/self-service/*) never runs,
    so the request never reaches Kratos and the browser hits a 404 / unrelated route.

The same problem occurs with Next.js basePath: "/app" configured in next.config.ts.

Relevant log output

GET /login 307
Location: https://example.com/self-service/login/browser?...
GET /self-service/login/browser 404

Relevant configuration

// src/lib/ory.ts
export const oryConfig: OryClientConfiguration = {
  project: {
    login_ui_url: '/login',
    registration_ui_url: '/registration',
    // ...
  },
};
// src/middleware.ts
import { createOryMiddleware } from '@ory/nextjs/middleware';
export const middleware = createOryMiddleware(oryConfig);

Version

@ory/nextjs@1.0.0-rc.1

On which operating system are you observing this issue?

Linux (containerised, behind ingress)

In which environment are you deploying?

Kubernetes (Helm), with an ingress mounting the app under a sub-path.

Additional Context

Source location:
src/app/flow.tsstartNewFlow.

Spec reference for the URL constructor behavior:
https://url.spec.whatwg.org/#concept-url-parser — an input starting with /
is parsed as a path-absolute URL and replaces base.pathname.

I'm opening a PR with the one-character fix.

Summary by CodeRabbit

  • Chores
    • Adjusted internal URL routing path construction for improved consistency.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 28, 2026

⚠️ No Changeset found

Latest commit: b258e73

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

@hersentino is attempting to deploy a commit to the ory Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 28, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

The startNewFlow function's redirect URL path is modified by removing the leading forward slash from the self-service route segment. The remainder of the URL construction logic remains unchanged.

Changes

Cohort / File(s) Summary
URL Path Modification
packages/nextjs/src/app/utils.ts
Removed leading / from the self-service route segment in the startNewFlow function's redirect target URL.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the bug being fixed: the absolute path issue in startNewFlow that breaks sub-path deployments.
Description check ✅ Passed The description comprehensively covers the bug, root cause, fix, backwards compatibility, reproduction steps, and relevant configuration, exceeding the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nextjs/src/app/utils.ts (1)

65-69: ⚠️ Potential issue | 🟠 Major

Relative path breaks when baseUrl has no trailing slash—baseUrl is normalized to strip trailing slashes in production

Line 65 assumes baseUrl has a trailing slash, but orySdkUrl() and getProjectApiKey() explicitly remove them. With new URL("self-service/...", "https://example.com/app"), the app segment is dropped instead of appending the flow path.

The codebase already has joinUrlPaths() utility (in packages/nextjs/src/utils/utils.ts) with tests covering this exact scenario. Either use that utility or implement the path-aware fix suggested below.

Suggested fix
 export function startNewFlow(
   params: QueryParams,
   flowType: FlowType,
   baseUrl: string,
 ) {
   // Take advantage of the fact, that Ory handles the flow creation for us and redirects the user to the default
   // return to automatically if they're logged in already.
-  return redirect(
-    new URL(
-      "self-service/" +
-        flowType.toString() +
-        "/browser?" +
-        urlQueryToSearchParams(params).toString(),
-      baseUrl,
-    ).toString(),
-    RedirectType.replace,
-  )
+  const flowUrl = new URL(baseUrl)
+  const basePath = flowUrl.pathname.endsWith("/")
+    ? flowUrl.pathname
+    : `${flowUrl.pathname}/`
+  const query = urlQueryToSearchParams(params).toString()
+
+  flowUrl.pathname = `${basePath}self-service/${flowType.toString()}/browser`
+  flowUrl.search = `?${query}`
+
+  return redirect(flowUrl.toString(), RedirectType.replace)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nextjs/src/app/utils.ts` around lines 65 - 69, The URL construction
currently concatenates "self-service/" + flowType + "/browser?" +
urlQueryToSearchParams(params).toString() with baseUrl which fails when baseUrl
has no trailing slash; update the code that builds the return URL in
packages/nextjs/src/app/utils.ts (the function using flowType,
urlQueryToSearchParams and baseUrl) to use the existing joinUrlPaths(...)
utility from packages/nextjs/src/utils/utils.ts (or implement equivalent
path-aware joining) to combine baseUrl and the relative path before appending
the query string so that base path segments are preserved even when baseUrl has
no trailing slash.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/nextjs/src/app/utils.ts`:
- Around line 65-69: The URL construction currently concatenates "self-service/"
+ flowType + "/browser?" + urlQueryToSearchParams(params).toString() with
baseUrl which fails when baseUrl has no trailing slash; update the code that
builds the return URL in packages/nextjs/src/app/utils.ts (the function using
flowType, urlQueryToSearchParams and baseUrl) to use the existing
joinUrlPaths(...) utility from packages/nextjs/src/utils/utils.ts (or implement
equivalent path-aware joining) to combine baseUrl and the relative path before
appending the query string so that base path segments are preserved even when
baseUrl has no trailing slash.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 82bdff4f-7602-4bbb-8a0d-0e24028979b3

📥 Commits

Reviewing files that changed from the base of the PR and between a6bbdbc and b258e73.

📒 Files selected for processing (1)
  • packages/nextjs/src/app/utils.ts

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.79%. Comparing base (f3fad4d) to head (b258e73).
⚠️ Report is 328 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #597       +/-   ##
===========================================
+ Coverage   42.43%   59.79%   +17.36%     
===========================================
  Files         136      182       +46     
  Lines        2008     3557     +1549     
  Branches      288      563      +275     
===========================================
+ Hits          852     2127     +1275     
- Misses       1149     1306      +157     
- Partials        7      124      +117     
Components Coverage Δ
@ory/elements-react 59.63% <ø> (+22.84%) ⬆️
@ory/nextjs 60.70% <ø> (-5.28%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hersentino hersentino marked this pull request as draft April 29, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants