Bug: guessPotentiallyProxiedOrySdkUrl Returns Wrong SDK Url for Staging (nextJS)
Preflight checklist
- [x] I could not find a solution in the existing issues, docs, nor discussions.
- [x] I agree to follow this project's Code of Conduct.
- [x] I have read and am following this repository's Contribution Guidelines.
- [x] I have joined the Ory Community Slack.
- [x] I am signed up to the Ory Security Patch Newsletter.
Ory Network Project
No response
Describe the bug
I have run into an issue with the ory/nextjs package, specifically the getLoginFlow function:
https://github.com/ory/elements/blob/e159960474eb1351bea86afab6d930f41b4acae6/packages/nextjs/src/app/login.ts#L47
We call this function when a user navigates to the login page of our site.
Under the hood it uses this function to determine the ory SDK Url: https://github.com/ory/elements/blob/e159960474eb1351bea86afab6d930f41b4acae6/packages/elements-react/src/client/config.ts#L46
In Vercel we have a production and a staging environment. This works fine for our production environment. But in our staging environment the user is sent to the wrong link, e.g. xyz.vercel.app because the logic in this function causes the us to hit this line which sets our sdk url as the incorrect address:
if (!isProduction() && process.env.VERCEL_URL) {
return `https://${process.env.VERCEL_URL}`.replace(/\/$/, "")
}
But we need the request to be directed to the address we set with this variable, even in staging: NEXT_PUBLIC_ORY_SDK_URL
Always very grateful for your help and continued support 🙏
Reproducing the bug
- Clone this example project https://github.com/ory/elements/tree/main/examples/nextjs-app-router
- Publish to Github
- Link the project in vercel and create a project from main branch.
- GIve the project an additional custom environment called "staging".
- Set the environment variable for each environment
NEXT_PUBLIC_ORY_SDK_URLas any value. - The login flow will work for production environment but fail for staging environment, with users being directed to
xyz.vercel.appwhen they initiate a login sequence
Relevant log output
Relevant configuration
Version
1.0.0-rc.0
On which operating system are you observing this issue?
None
In which environment are you deploying?
None
Additional Context
No response
I'm running into something similar. Is your production environment at a *.vercel.app domain as well?
In my case the issue is coming from the function by the same name in the nextjs tree:
https://github.com/ory/elements/blob/a708dd081ada5cf02edffb4e52e83971457ef9b7/packages/nextjs/src/utils/sdk.ts#L28-L73
It's called in getLoginFlow.
During development (localhost), everything works fine because it hits the return options.knownProxiedUrl line, which returns localhost, and thus the redirect induced by getLoginFlow (to /self-service/login/browser) is handled by my app (via the interceptor in the ory middleware).
However in production, which is not in Vercel at all let alone a vercel.app domain (we're on Azure), we bump into the following clause owing to NODE_ENV == "production"
https://github.com/ory/elements/blob/a708dd081ada5cf02edffb4e52e83971457ef9b7/packages/nextjs/src/utils/sdk.ts#L52-L55
The problem is that our app is not hosted by Ory. We need this redirect to be back to https://ourdomain.com like it is to https://localhost in development. But we can't set up the custom domain in Ory to serve the orySdk from https://ourdomain.com, because that needs to point to our app in Azure.
In terms of the solution, for NextJS its unclear to me that you want any special handling here at all. guessPotentiallyProxiedOrySdkUrl is only used by what appear to be server-side-nextjs specific methods that redirect to self-service/, which is handled by the ory middleware. So I think you want to just get rid of guessPotentiallyProxiedOrySdkUrl and always use getPublicUrl.
We are hitting the same problem at arcade.dev ...
In Vercel we have a production and a staging environment. This works fine for our production environment. But in our staging environment the user is sent to the wrong link, e.g. xyz.vercel.app because the logic in this function causes the us to hit this line which sets our sdk url as the incorrect address:
Does your staging build not use NODE_ENV=production?
However in production, which is not in Vercel at all let alone a vercel.app domain (we're on Azure), we bump into the following clause owing to NODE_ENV == "production"
I don't understand your problem - if you bump into this section of the code just set the environment variable correctly?
We are hitting the same problem at arcade.dev ...
Please provide a reproducible example like in the other posts so we understand the problem, thanks!
@evantahler To add to the above - as a customer you also have access to dedicated support, please open a ticket. It will get it resolved a lot quicker...
@ibeckermayer, I have taken a look at your configuration, and I am not quite sure if I understand the issue you're running into.
To me, this seems like a misconfiguration in your project, not a bug in the code.
We only use guessPotentiallyProxiedOrySdkUrl to talk to the Ory project directly. Not your own domains. For development mode, this needs to be localhost, because of cookies and the middleware we provide that actually forwards these requests to your Ory project. But in production(-like) environments, this needs to be the CNAME you configured in your Ory project. This is, again, because of cookies:
Ory's APIs issue CSRF and session cookies on the domains the user is on. It cannot do this for domains that are not subdomains of your "main" domain used by your application, because third party cookies are very unreliable and work differently in every browser.
Now, some parts of the Ory flows redirect back to your configured UI, and this can be on any (sub-)domain you want as long as we can set cookies on it. But the user's browser first needs to hit Ory APIs for us to set the cookies.
So: in production-like environments, set the ORY_SDK_URL env variable to your project's CNAME and make it's a subdomain of your main app (e.g. auth.my.app or account.my.app).
I don't understand your problem - if you bump into this section of the code just set the environment variable correctly?
That variable is set correctly, in the sense that it's set to our ORY_SDK_URL (e.g. https://clever-taussig-xbcwhlp8ws.projects.oryapis.com). The problem is, this function is deciding a redirect url, and we don't ever want our users to be redirected to an Ory hosted page. We are using the ory nextjs components in our application, we only ever want them to redirected to our application.
We only use guessPotentiallyProxiedOrySdkUrl to talk to the Ory project directly. Not your own domains.
I think you're mistaken about this. We have a next.js component that is the example given in getLoginFlow, verbatim:
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/app/login.ts#L22-L40
That function calls guessPotentiallyProxiedOrySdkUrl
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/app/login.ts#L59-L61
to get the baseUrl parameter of the getFlowFactory function.
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/app/flow.ts#L32
That parameter ultimately culminates in a browser redirect:
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/app/flow.ts#L44-L49
This is where our problem lies. In development, that redirect is our public url (e.g. localhost:3000) which means the new flow id gets routed through the ory middleware and everything works as expected. However the logic of guessPotentiallyProxiedOrySdkUrl means that in production, that redirect becomes the ory sdk url, which is something like https://clever-taussig-xbcwhlp8ws.projects.oryapis.com, which points to Ory's servers. It makes no difference whether that's https://clever-taussig-xbcwhlp8ws.projects.oryapis.com or whether I've configured my DNS such that it's https://auth.mydomain.com -- either way, it's a browser redirect to Ory servers, which my users should never browse to directly.
If we add yarn patch like
diff --git a/dist/app/index.js b/dist/app/index.js
index d4ade92a6897ee5f111619b7daf0b6d0a761fded..56a65f33e6d781d547dedcfd518414a9aff7581d 100644
--- a/dist/app/index.js
+++ b/dist/app/index.js
@@ -190,9 +190,7 @@ async function getLoginFlow(config, params) {
initOverrides
),
clientFetch.FlowType.Login,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.login_ui_url
);
}
@@ -204,9 +202,7 @@ async function getRegistrationFlow(config, params) {
initOverrides
),
clientFetch.FlowType.Registration,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.registration_ui_url
);
}
@@ -218,9 +214,7 @@ async function getRecoveryFlow(config, params) {
initOverrides
),
clientFetch.FlowType.Recovery,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.recovery_ui_url
);
}
@@ -232,9 +226,7 @@ async function getVerificationFlow(config, params) {
initOverrides
),
clientFetch.FlowType.Verification,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.verification_ui_url
);
}
@@ -246,9 +238,7 @@ async function getSettingsFlow(config, params) {
initOverrides
),
clientFetch.FlowType.Settings,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.settings_ui_url
);
}
@@ -257,10 +247,7 @@ async function getLogoutFlow({
} = {}) {
var _a;
const h = await headers.headers();
- const knownProxiedUrl = await getPublicUrl();
- const url = guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl
- });
+ const url = await getPublicUrl();
return serverSideFrontendClient().createBrowserLogoutFlow({
cookie: (_a = h.get("cookie")) != null ? _a : "",
returnTo
diff --git a/dist/app/index.mjs b/dist/app/index.mjs
index da6b25477c72c6e828734a05ede2979dc7fca911..51b648b15ca966c27d49b1a1c3ac5ba6f9041f8c 100644
--- a/dist/app/index.mjs
+++ b/dist/app/index.mjs
@@ -188,9 +188,7 @@ async function getLoginFlow(config, params) {
initOverrides
),
FlowType.Login,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.login_ui_url
);
}
@@ -202,9 +200,7 @@ async function getRegistrationFlow(config, params) {
initOverrides
),
FlowType.Registration,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.registration_ui_url
);
}
@@ -216,9 +212,7 @@ async function getRecoveryFlow(config, params) {
initOverrides
),
FlowType.Recovery,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.recovery_ui_url
);
}
@@ -230,9 +224,7 @@ async function getVerificationFlow(config, params) {
initOverrides
),
FlowType.Verification,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.verification_ui_url
);
}
@@ -244,9 +236,7 @@ async function getSettingsFlow(config, params) {
initOverrides
),
FlowType.Settings,
- guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl: await getPublicUrl()
- }),
+ await getPublicUrl(),
config.project.settings_ui_url
);
}
@@ -255,10 +245,7 @@ async function getLogoutFlow({
} = {}) {
var _a;
const h = await headers();
- const knownProxiedUrl = await getPublicUrl();
- const url = guessPotentiallyProxiedOrySdkUrl({
- knownProxiedUrl
- });
+ const url = await getPublicUrl();
return serverSideFrontendClient().createBrowserLogoutFlow({
cookie: (_a = h.get("cookie")) != null ? _a : "",
returnTo
everything works for us, including in production.
There are no cookie issues in this nextjs case, because afaict Ory is never sending cookies directly to us in the browser. IIUC all cookies are being set in calls that get intercepted by the ory middleware (which lives server side)
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/middleware/middleware.ts#L46-L56
and domains are rewritten before being returned to the browser:
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/middleware/middleware.ts#L102-L114
@jonas-jonas I've created an app with our basic project structure here:
https://github.com/ibeckermayer/ory-reproducible
(These codepaths are pretty difficult to follow, particularly with the added complexity of nextjs server vs client and the ory middleware proxy; I certainly could be mistaken, but you may be confusing the nextjs app with the workings of a SPA react app).
either way, it's a browser redirect to Ory servers, which my users should never browse to directly.
Actually, you should ALWAYS point users DIRECTLY to your Ory CNAME and NEVER proxy requests to Ory, as it significantly weakens our ability to protect your login from bots and malicious actors.
The ORY_SDK_URL should generally point to your cname, not to the oryapis.com domain (oryapis only during development). We probably have to clarify this in our docs @unatasha8 @wassimoo
Instead, you set your ORY_SDK_URL to your Ory Network CNAME (e.g. ory.domain.com) and set your default_redirect_url to your application's main entrypoint.
Actually, you should ALWAYS point users DIRECTLY to your Ory CNAME and NEVER proxy requests to Ory, as it significantly weakens our ability to protect your login from bots and malicious actors.
If this is the case then why do you provide the Login react component, and why does your official nextjs middleware proxy requests to Ory? Are you saying that you don't support self-hosting the login page (in which case, again, why would you provide an official React component seemingly just for that purpose?)
I think you're mistaken, and that you do support what I'm trying to do, but only for deploying the app to Vercel:
https://github.com/ory/elements/blob/b75405742b4c9154d421acaf04b5eee064053289/packages/nextjs/src/utils/sdk.ts#L31-L50
If you think otherwise, can you point me to specifically what's wrong with the reproducible example I provided?
https://github.com/ibeckermayer/ory-reproducible
In vercel we only support it for local dev and preview environments where you cant easily set a custom domain. In production vercel you set the SDK_URL to your cname.
It sounds like you may be mixing up the different options, which I understand because it‘s not well documented. Ideally reach out to a support person to figure out exactly how to set it up. I‘m sure your use case works, it‘s just not configured correctly on your end
How do I properly contact support? I have a thread with @jonas-jonas in Slack, and this one here. Is there an alternative support channel I should use?
@ibeckermayer if you are an Ory customer you can contact [email protected] or open a ticket at Dedicated Support at https://console.ory.sh/support