Shopify / quilt

[⚠️ Deprecated] A loosely related set of packages for JavaScript/TypeScript projects at Shopify
MIT License
1.7k stars 220 forks source link

Embedding of app inside Shopify admin UI with @shopify/koa-shopify-auth not working / redirect occurs #318

Closed bastiankistner closed 6 years ago

bastiankistner commented 6 years ago

Overview

What I want to achieve

What's happening

  1. authentication works fine, token is being exchanged
  2. as soon as my app loads, it escapes the Shopify admin UI and the browser redirects to my app url

What I implemented and tried

What makes me curious / Questions

Docs are saying that we should escape the iframe before authentication is happening, which @shopify/koa-shopify-auth seems to be doing in redirection-page. I assume the containing script is what the shopify browser lib is doing under the hood?

If I serve a polaris app instead of the markup that also disables the forceRedirect, what happens if my react app takes some time to load? Documentation is also saying that ShopifyApp.init(shopifyAppConfig); should happen immediately.

A comment in app.ejs also mentions that the redirect will only happen during development if it's not disabled. Does this mean it will at least work when my app is published?

I do remember that I had implemented a test app a few months ago with the express version of the auth flow and the app was being displayed inside the admin UI during development. Although I don't have the code anymore 😕

Am I missing something or is this really a bug? I'm really desperate and your opinion is very much appreciated! I can also give you access to my private repo if that might be of help.

Consuming repo

https://github.com/Shopify/quilt

Additional resources

test app being served after successful auth

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>woop</title>
  </head>
  <body>
    <script src="https://cdn.shopify.com/s/assets/external/app.js"></script>

    <script>
      window.apiKey = ${API_KEY}
      window.shopOrigin = "https://${shop}"
      const shopifyAppConfig = {
        apiKey: window.apiKey,
        shopOrigin: window.shopOrigin,
      }

        shopifyAppConfig.forceRedirect = false;

      ShopifyApp.init(shopifyAppConfig);
    </script>

    <div id="root">🙆‍♂️</div>
  </body>
</html>
bastiankistner commented 6 years ago

I tried to further analyse the reasons behind that behaviour and found out the following:

shopify-express is doing an additional check here where it's looking for the accessToken in the current session. If it's availabe, it just skips all further checks and redirects to /

koa-shopify-auth is acting differently. It's always running into the topLevelOAuthRedirect here

This leads to a reauthentication and a jump out of the admin UI.

The same happens for shopify-express if you restart the server, as it's additionally storing the the shop and accessToken via a memory strategy on the server. When a restart occurs, the accessToken on the server session is lost and a re-auth is required.

bastiankistner commented 6 years ago

I have just tested @shopify/koa-shopify-auth again as it is currently available through npm and indeed, it is creating a new access token every time I reload my app in shopify admin.

I'm giving up on it for now. I am missing details related to shouldPerformInlineOAuth, TOP_LEVEL_OAUTH_COOKIE_NAME and TEST_COOKIE_NAME. I feel like this should have something to do with the issue?

ragalie commented 6 years ago

:wave: hi! Sorry you're having trouble :(

Here are the instructions we used to test out the implementation: https://github.com/Shopify/quilt/pull/268#issue-212953768

It involves cloning the unite-react-node-app-workshop repo and then replacing the server/index.js with a modified one that uses the koa-shopify-auth package.

Can you see if you're able to get it working properly in that environment? If so, that might help you narrow down why your implementation is failing to stay inside the iframe.

This isn't as turnkey as we'd like right now, and we're actively working on improving our development tools to make it really straightforward to get started.

Let me know if this helps and I'll keep an eye on this thread in case you still end up having trouble.

bastiankistner commented 6 years ago

Thank you very much for your help!

I tried the example in unite-react-node-app-workshop with the following server as index.js. Installing the app works, but then the callback route /shopify/auth/callback is returning a 404 and I'm getting Not found. as response.

That's really weird since I haven't had this behaviour before. Maybe you have an idea? I didn't change anything else.

App Urls being used: entry: https://{tunnelname}.eu.ngrok.io/shopify

whitelisted urls: https://{tunnelname}.eu.ngrok.io/shopify/auth/callback

{tunnelname} being my named ngrok tunnel

I slightly modified the server file to see the request paths being called. I'm not using the baseUrl as I could only find those in the tests. And I upgraded koa-shopify-auth to 3.1.3.

import 'isomorphic-fetch';

import dotenv from 'dotenv';
import Koa from 'koa';
import session from 'koa-session';
import shopifyAuth, {verifyRequest} from '@shopify/koa-shopify-auth';
dotenv.config();

const {SHOPIFY_API_KEY, SHOPIFY_SECRET} = process.env;

const app = new Koa();
app.keys = [SHOPIFY_SECRET];

app
  // sets up secure session data on each request
  .use(session(app))
  .use((ctx, next) => {
    console.log(ctx.request.path);
    next();
  })

  // sets up shopify auth
  .use(
    shopifyAuth({
      apiKey: SHOPIFY_API_KEY,
      secret: SHOPIFY_SECRET,
      //   baseUrl: '', // 💡 removed as it's not being used in sources, only in tests
      scopes: ['write_orders, write_products'],
      prefix: '/shopify',
      afterAuth(ctx) {
        const {shop, accessToken} = ctx.session;

        console.log('We did it!', accessToken);

        ctx.redirect('/');
      },
    }),
  )

  // everything after this point will require authentication
  .use(
    verifyRequest({authRoute: '/shopify/auth', fallbackRoute: '/shopify/auth'}),
  )

  // application code
  .use((ctx) => {
    console.log('sending body');
    ctx.body = `
    <script src="https://cdn.shopify.com/s/assets/external/app.js"></script>

    <script type="text/javascript">
      ShopifyApp.init({
        apiKey: "${SHOPIFY_API_KEY}",
        shopOrigin: "https://${ctx.session.shop}",
        debug: true,
        forceRedirect: true
      });
    </script>

    🎉
    `;
  });

export default app;
ragalie commented 6 years ago

I don't know why this is (I'm not super well-versed on koa in particular), but when I remove the path logging it seems to work for me:

  /*.use((ctx, next) => {
    console.log(ctx.request.path);
    next();
  })*/
bastiankistner commented 6 years ago

I think I still had a modified version of koa-shopify-auth running, which was the reason for my issue. Reinstalling the module helped. Even with the path logging. Thank you so much @ragalie ! I'll integrate this into my project and will leave a note when everything works.

bastiankistner commented 6 years ago

Works so far. But it seems I can't reinstall the app unless I manually delete the cookies for my app domain.

If I don't delete them, shopify is showing a page saying The page you're looking for couldn't be found Check the web address and try again, or try navigating to the page from Shopify Home.

ragalie commented 6 years ago

Hmmm, I wasn't able to reproduce that. I was able to successfully reinstall by doing the following:

  1. I added a new scope to the list of scopes (which should trigger a reinstall).
  2. I torched the session (I just added ctx.session = {} to the last use function, but I think koa-session supports something nicer).
  3. I attempted to access the app and it prompted me to accept the new scopes and then redirected properly.

What are you trying that's resulting in the error message?

bastiankistner commented 6 years ago

I manually removed the app from my test store and triggered a reinstall for this store through my partner account.

I was able to reproduce it multiple times. But I won't be able to access my mac before Friday.

Most important to me is that I can continue now as long as I'm aware of what to take care of. But I'll look into the cookie issue and certainly let you know how I triggered this error page in detail as soon as possible

ragalie commented 6 years ago

Thanks for the update!

This error happens because your app thinks the merchant is still logged in (due to the cookie), but in reality the app has been uninstalled, so Shopify refuses to load the app.

I'm aware of some apps that listen to the app/uninstalled webhook (https://help.shopify.com/en/api/reference/events/webhook) and then remove the shop token for that shop when their app is uninstalled. That way the app knows that the shop has uninstalled the app, and redirects to the OAuth flow instead of redirecting to the Shopify embedded app URL.

Since the original issue has been resolved, I'm going to close out this issue. Thanks again for participating!

bastiankistner commented 6 years ago

Sounds reasonable. Thank you very much for your help @ragalie

bastiankistner commented 6 years ago

@ragalie I finally found the root cause of my issue. What I wanted to do was passing all app config data dynamically to the authentication middleware of koa-shopify-auth to use the middleware for multiple shopify apps while having a single point of authentication. Therefore, I was wrapping all middleware into a (ctx, next) => ... koaShopifyMiddleware(config)(ctx,next) function. And obviously I did not use async for all my middlewares. So when the fetch for the access token occurred, koa already ended the request because the request.path logging finished long before the authCallback resolved as it was synchronous.

When we however add an async to each and every middleware / wrapper, everything works as it should.

I'm also usually using express and am quite new to koa. Lesson learned :D

Thanks again so much !!!!

ryomato commented 5 years ago

@ragalie Hi, I just started with shopify-koa-auth and I was redirected to /auth/enable_cookies & then to Shopify embedded app URL instead of OAuth Path (/auth?shop={shop}) everytime -> The page you're looking for couldn't be found Check the web address and try again, or try navigating to the page from Shopify Home

juliantrueflynn commented 5 years ago

@ryomato late response, but in case anyone else runs into this issue finds this ticket from search: I found you can also get this message if ngrok is a different port than your server making the request.

andrewckor commented 5 years ago

A bit late to the thread but I just figured out what is the solution to make your app rendered inside the Shopify admin without a redirect.

There is a setting that is not well documented in the repo nor the partners' documentation.

When you define the App URL in your app's settings page, you can simply switch the URL to e.g. https://kdkf92n34.ngrok.io/auth/inline and that makes your app to not trigger a top-level redirect.

Also check the screenshot: CleanShot 2019-08-17 at 17 25 22@2x

Here is the source code of this: https://github.com/Shopify/quilt/blob/22a1ce062d95c425b390ffbd0919fc03594da072/packages/koa-shopify-auth/src/auth/index.ts#L41

I hope it will help any future developer that is having the same issue.

mvhirsch commented 5 years ago

Looks like this results in an "Request origin could not be verified". See here: https://github.com/Shopify/quilt/issues/791#issuecomment-524315739

I'm getting this on Safari and Chrome, but not Firefox.

felipeloha commented 3 years ago

I am getting "Oauth error invalid_request: The redirect_uri is not whitelisted" when I use the inline