koa-shopify-auth redirect callback url

hasanagh
Tourist
8 0 3

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.

Replies 15 (15)

composed
Excursionist
10 0 5

I would look into setting your env variables on the hosting platform. 

Here’s the docs for Heroku:

https://devcenter.heroku.com/articles/config-vars

kurt-shipad
Tourist
6 1 0

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

 error2.pngerror1.png

Please advise. Thank you very much.

kurt-shipad
Tourist
6 1 0

Additional info: my app is hosted on a Windows server, using IIS as reverse proxy and pm2 to manage node processes.

hasanagh
Tourist
8 0 3

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?

kurt-shipad
Tourist
6 1 0

Yes, I use koa-shopify-auth.

 

hasanagh
Tourist
8 0 3

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.

composed
Excursionist
10 0 5

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:

Screen Shot 2020-08-24 at 11.31.27 AM.png

 

Hope that helps!

hasanagh
Tourist
8 0 3

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.

composed
Excursionist
10 0 5

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:

Screen Shot 2020-08-24 at 12.00.53 PM.png

 

hasanagh
Tourist
8 0 3

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.

composed
Excursionist
10 0 5

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.

kurt-shipad
Tourist
6 1 0

Hi 

composed
Excursionist
10 0 5

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!

composed
Excursionist
10 0 5

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. 

hasanagh
Tourist
8 0 3

@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