Questions and discussions about using the Shopify CLI and Shopify-built libraries.
Hey guys,
I am using "@shopify/koa-shopify-auth" for my application and I followed the Node/React tutorial, except I am not using next.js but separate node and react applications.
Locally, everything is working fine. But in production (I have each deployed to a different domain), I am getting a wrong redirect_url which has the hostname as my backend host instead of the frontend.
i.e. https://{BACKEND_HOSTNAME}/auth/callback
What might be the reason for this?
Thank you for your help.
I would look into setting your env variables on the hosting platform.
Here’s the docs for Heroku:
I have same problem. I followed the tutorial, everything works fine with localhost (use ngrok). But when I deploy the app to production and use real domain name for the app, it redirect to
https://app-domain.com/admin/auth/authorize?state=xxx&scope=xxx&client_id=xxx&redirect_uri=https%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback&grant_options%5B%5D=per-user
Finally end up with error:
Expected a valid shop query parameter
Please advise. Thank you very much.
Additional info: my app is hosted on a Windows server, using IIS as reverse proxy and pm2 to manage node processes.
Hey kurt,
I think your problem is different than mine. The redirect url appears as localhost in your case which I think is related to a problem in your code/setup.
And, what do you use for authentication? koa-shopify-auth?
Yes, I use koa-shopify-auth.
ok.
So for me, the issue was because koa-shopify-auth defaults the host of the redirect URL to the host of the request.
This host is changed by the reverse proxy. For me, I had to configure Nginx myself for it to work.
I also opened an issue to make this configurable.
You will probably need to change your HOST variable in your .env file at the root of your project.
If we look inside the koa-shopify-auth package, we can see the following:
export default function createOAuthStart(
options: OAuthStartOptions,
callbackPath: string,
) {
return function oAuthStart(ctx: Context) {
const {myShopifyDomain} = options;
const {query} = ctx;
const {shop} = query;
const shopRegex = new RegExp(
`^[a-z0-9][a-z0-9\\-]*[a-z0-9]\\.${myShopifyDomain}$`,
'i',
);
if (shop == null || !shopRegex.test(shop)) {
ctx.throw(400, Error.ShopParamMissing);
return;
}
ctx.cookies.set(TOP_LEVEL_OAUTH_COOKIE_NAME, '', getCookieOptions(ctx));
const formattedQueryString = oAuthQueryString(ctx, options, callbackPath);
ctx.redirect(
`https://${shop}/admin/oauth/authorize?${formattedQueryString}`,
);
};
}
The redirect is being created in the oAuthQueryString module. If we take a look in that module, the redirect_uri is being created with your process.env.HOST variable, which is being propagated through the Koa context.
export default function oAuthQueryString(
ctx: Context,
options: OAuthStartOptions,
callbackPath: string,
) {
const {host, cookies} = ctx;
const {scopes = [], apiKey, accessMode} = options;
const requestNonce = createNonce();
cookies.set('shopifyNonce', requestNonce, getCookieOptions(ctx));
/* eslint-disable @typescript-eslint/camelcase */
const redirectParams = {
state: requestNonce,
scope: scopes.join(', '),
client_id: apiKey,
redirect_uri: `https://${host}${callbackPath}`,
};
/* eslint-enable @typescript-eslint/camelcase */
if (accessMode === 'online') {
redirectParams['grant_options[]'] = 'per-user';
}
return querystring.stringify(redirectParams);
}
This is fine to have set as https://localhost:3000 when you're in dev mode, but in production, you'll need to change the environment variable in the production environment. I haven't used IIS in a long time, but I'm sure there's a way to set your HOST var to your production domain.
In Heroku, for example, you can set it within the web app for the deployment, or on the CLI.
Here's a screenshot of a production embedded app's config vars in Heroku:
Hope that helps!
hello @composed thank you for your detailed reply.
Are you sure koa's host defaults to the environment variable and not the host from the request headers? Because I think that's the case.
Sure, np. You're right, the host is represented in ctx.request.header.host on requests, but for creating the callback it's pulling from process.env.HOST.
This is being set with the package dotenv and merged back into the process.env variables:
https://www.npmjs.com/package/dotenv
When you're running in production, the production host will need to configure this host by pulling from the config variables set on the hosting platform. You will want to mirror the same environment variables as in your .env file in dev, but use the production domain for the HOST.
So in the case of using the Shopify CLI, they are using ngrok to tunnel into the localhost from the outside. So the Shopify CLI will set process.env.HOST to the ngrok tunnel URL when it starts the server.
I had this problem when I deployed to Heroku, and had to change my Heroku config vars and it fixed the issue.
Also, if you are using Nextjs in the project, like what's generated in the Shopify CLI, you can access the env variables on the front end if necessary by prepending "NEXT_PUBLIC_" on the variable in the .env file.
https://nextjs.org/docs/basic-features/environment-variables
You can see the .env file set to the ngrok tunnel below:
Hello @composed, yes I agree with you. But regarding what you said here:
the host is represented in ctx.request.header.host on requests, but for creating the callback it's pulling from process.env.HOST.
I can't see where the pulling for the host from process.env.Host is happening, I can see it clearly from the
ctx.request.header.host
as you mentioned.
When using next you won't face the mentioned because both the frontend and backend will be on the same origin. In my case, I am had different origins as I am not using next this is why I had to change the reverse proxy configuration and rely on Koa's proxy headers.
It is interesting that someone replied to this issue hahaha I really thought that I am the only one facing it while it should have been a common one.
I see what you're saying, I'm actually a little stumped at the moment. I can see the destructuring happening inside the oAuthQueryString module like this:
const { host } = ctx;
But I took the context object and pulled it into a separate Node REPL and tried destructuring host out of ctx myself and it's undefined.
I don't see a host key at the top level of that object. Could it be reaching down into the header object implicitly? That just seems unlikely, especially since I can't recreate it.
It's a little magical, not sure really. I'd actually be interested in knowing what's happening here on this particular assignment.
Hi hasanagh and composed,
In my case, it redirects to wrong URL:
http://app-domain/admin/oauth/authorize?state=159833889306800&scope=read_products%2C%20read_orders%2C%20read_inventory&client_id=fe96ed46fe7831fddfc9d7e0f28abc67&redirect_uri=https%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback&grant_options%5B%5D=per-user
There're 3 wrong parts in this URL:
- red part is http. In createOAuthStart function, I see they set https ctx.redirect("https://" + shop + "/admin/oauth/authorize?" + formattedQueryString); Not sure why it is http here
- blue part is the domain. I got my app domain here, but I know it should be the shop domain
- green part is redirect_uri, I got localhost but it should be the app domain. I followed your comments and setup IIS to preserve host header in proxy and it does change to my app domain. Thanks.
Do you have any idea about the red and blue parts?
Hi @kurt-shipad , not sure about your situation. I have a hunch it's probably mostly related to the IIS and proxy settings. Also you might want to check your DNS registrar.
In my situation with Heroku, they will give you a unique subdomain like *.herokudns.com to create a CNAME record for. You will also want to make sure you are using SSL on your hosting platform, so either you need to configure your own cert or they will do it for you.
Then, like mentioned above, make sure you create environment variables on the hosting platform to match your .env for local dev.
Beyond that, I'm afraid I'm not much help b/c it's probably an IIS thing.
Hope that helps somewhat. Good luck!
Ok, sorry if the answer is already super obvious, but I probably should have read the Koa source first. Looks like the reason host isn't on the ctx object is because it's a getter. You can see this here:
/**
* Parse the "Host" header field host
* and support X-Forwarded-Host when a
* proxy is enabled.
*
* @return {String} hostname:port
* @api public
*/
get host() {
const proxy = this.app.proxy;
let host = proxy && this.get('X-Forwarded-Host');
if (!host) {
if (this.req.httpVersionMajor >= 2) host = this.get(':authority');
if (!host) host = this.get('Host');
}
if (!host) return '';
return host.split(/\s*,\s*/, 1)[0];
},
Anyway, @hasanagh you're right, it looks like it's pulling host from the request header.
@composed exactly!
If you really agree with me that we should be able to add a custom host for the redirect URL please second my issue here https://github.com/Shopify/quilt/issues/1593