koa-shopify-auth icon indicating copy to clipboard operation
koa-shopify-auth copied to clipboard

v4: This app can’t load due to an issue with browser cookies

Open janklimo opened this issue 3 years ago • 33 comments

After upgrading to 4.0.2, my app fails to authenticate and load as an embedded app (using ngrok).

After a series of redirects it fails:

Screen Shot 2021-03-04 at 14 28 20

This was working normally under v3.2.0 so maybe you can point me in the right direction knowing what has been changed since then. Wondering if other users are facing the issue so we could update the docs if I'm missing something obvious.

My server.js code:

const blitz = require("@blitzjs/server");
const Koa = require("koa");
const { default: shopifyAuth, verifyRequest } = require("@shopify/koa-shopify-auth");
const { default: Shopify, ApiVersion } = require("@shopify/shopify-api");
const session = require("koa-session");
const { PrismaClient } = require("@prisma/client");
const Queue = require("bull");

const dev = process.env.NODE_ENV !== "production";
const port = parseInt(process.env.PORT, 10) || 3000;
const { SHOPIFY_API_SECRET_KEY, SHOPIFY_API_KEY, HOST_NAME } = process.env;
const REDIS_URL = process.env.REDIS_URL || "redis://127.0.0.1:6379";

const app = blitz({ dev });
const prisma = new PrismaClient();
const handle = app.getRequestHandler();
const workQueue = new Queue("work", REDIS_URL);

// Initialize the library for Shopify API
Shopify.Context.initialize({
  API_KEY: SHOPIFY_API_KEY,
  API_SECRET_KEY: SHOPIFY_API_SECRET_KEY,
  SCOPES: ["write_script_tags", "read_themes", "write_themes"],
  HOST_NAME,
  API_VERSION: ApiVersion.January21,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

app.prepare().then(() => {
  const server = new Koa();
  server.use(session({ sameSite: "none", secure: true }, server));
  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(
    shopifyAuth({
      // Tried both online and offline
      accessMode: "online",
      async afterAuth(ctx) {
        const { accessToken, shop: shopifyDomain } = ctx.state.shopify;

        // Migrate to cookieless sessions
        // ctx.cookies.set("shopOrigin", shopifyDomain, {
        //   httpOnly: false,
        //   secure: true,
        //   sameSite: "none",
        // });

        await workQueue.add();

        await prisma.shop.upsert({
          where: {
            shopifyDomain,
          },
          update: {
            shopifyToken: accessToken,
          },
          create: {
            shopifyDomain,
            shopifyToken: accessToken,
          },
        });

        ctx.redirect(`/?shop=${shopifyDomain}`);
      },
    })
  );

  /**
   * Everything after this point will require authentication.
   */
  server.use(verifyRequest({ accessMode: "online" }));

  server.use(async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  });

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

janklimo avatar Mar 04 '21 07:03 janklimo

Hey @janklimo, the problem here is that you are calling verifyRequest for the / endpoint (and for static pages). With the new session token authentication, your app should always load a page skeleton without expecting a session, so it can build the App Bridge client and set up the JWT session.

It's important to note that you should not include any sensitive information in that initial request since it needs to be unauthenticated.

The React + Node tutorial has been updated to follow this new pattern (in particular, steps 5-7).

Let me know if that helps unblock you, and we can mention that in the migration guide as well! In any case, we should also remove koa-session from the example app since it's no longer needed, and make sure that it follows the new pattern.

paulomarg avatar Mar 04 '21 14:03 paulomarg

Hey @paulomarg, I am trying to implement this new pattern. I am getting inconsistent error messages when I redirect to /auth: "Cannot complete OAuth process. No session found for the specified shop url:". These errors are not triggered each time and I can not understand why.

The redirect is handled by a KOA middleware, I am using Mongodb Atlas to store the shop data:

const verifySession = async (ctx, next) => {
  const shop = ctx.query.shop;

  // DB is Mongodb Atlas
  // Detect if shop is added to db, has passed trought OAuth
  let result = await DB.find({
    id: 'offline_' + shop
  }).exec();
  
  if (!result.length) {
     // Go to OAuth
     ctx.redirect(`/auth?shop=${shop}`);
     return;
  }

  await next();
};

router.get('/', verifySession, handleRequest)

Regarding your comment, what does actually means: so it can build the App Bridge client and set up the JWT session. Thank you

mirceapiturca avatar Mar 06 '21 13:03 mirceapiturca

Cannot complete OAuth process. No session found for the specified shop url:". These errors are not triggered each time and I can not understand why.

I am experiencing the same intermittent error message.

mmccall10 avatar Mar 06 '21 18:03 mmccall10

@mmccall10 @mirceapiturca can you guys share your full server.js file? How's the session persisted? I'm not seeing those, what @paulomarg recommended worked for me.

janklimo avatar Mar 06 '21 18:03 janklimo

accessToken I'm getting back is a short one. I was expecting long bearer token which I could decode. Is it expected? I'm using offline param that I pass same to verifyRequest as mentioned here.

Session output:

Session {
  id: 'offline_123.myshopify.com',
  shop: '123.myshopify.com',
  state: '5344458270303541',
  isOnline: false,
  accessToken: 'shpat_d8378c1996752bf8jdhc75b250926080',
  scope: 'write_script_tags,write_themes'
}

I will share server.js once I'm done with some cleanup. Currently its large.

avocadoslab avatar Mar 06 '21 20:03 avocadoslab

@mirceapiturca it's hard to say why you're getting that intermittent error, but that message happens when your app tries to load a session that has not been created yet. Your code with MongoDB looks right to me, so it could be that your loadSession callback for the Shopify.Context.SESSION_STORAGE class isn't returning the session.

@pratiknikam the accessToken you get back is used by your app to make requests to the Admin API (for example, using new Shopify.Clients.Rest(session.shop, session.accessToken)).

Regarding your comment, what does actually means: so it can build the App Bridge client and set up the JWT session.

Embedded apps using session tokens need to use the App Bridge app in their frontend. The flow is essentially this:

  1. App Bridge automatically creates the JWT session for you when you use the app in your frontend.
  2. When you call authenticatedFetch it automatically sends the JWT Bearer token with your request.
  3. Your server can then load the session from that JWT using e.g. Shopify.Utils.loadCurrentSession(ctx.req, ctx.res)
  4. As I mentioned above, with that session you can make requests.

The tutorial page on Apollo requests shows an example of how to use App Bridge + JWT tokens to load data from Shopify. If you follow it along from the start, it goes over all of the steps needed to create an app that can fetch data using session tokens.

Hope this helps!

paulomarg avatar Mar 08 '21 14:03 paulomarg

@janklimo @paulomarg I've actually managed to replicate this on a fresh install. The only change was adding accessMode: 'offline'.

A bit context Using a fresh install MemorySessionStorage Access mode: offline Using Ngrock locally.

Installing the application works fine, no errors. Restarting the server, and accessing the app from the admin, redirects to auth but gives intermittent errors. Please see this screen recording: https://www.loom.com/share/0b06d220f46541b5b06b1d732e52bf8b

Here is the server:

import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";

dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(
    createShopifyAuth({
      accessMode: 'offline',
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topic, shop, body) =>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });

        if (!response.success) {
          console.log(
            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
          );
        }

        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/?shop=${shop}`);
      },
    })
  );

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  router.get("/", async (ctx) => {
    const shop = ctx.query.shop;

    // This shop hasn't been seen yet, go through OAuth to create a session
    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });

  router.post("/webhooks", async (ctx) => {
    try {
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (error) {
      console.log(`Failed to process webhook: ${error}`);
    }
  });

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
  router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions

  server.use(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

Thank you

mirceapiturca avatar Mar 08 '21 14:03 mirceapiturca

Hey @mirceapiturca, I think all you're lacking is to change the line

router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions

to

router.get("(.*)", verifyRequest({accessMode: 'offline'}), handleRequest); // Everything else must have sessions

Your app is trying to load an online session on requests, but it's performing OAuth for offline sessions.

paulomarg avatar Mar 08 '21 15:03 paulomarg

@mirceapiturca said

Using Ngrock locally.

@paulomarg This might be a long shot but could this be in any way related to ngrok/tunneling? No idea how that would work but I saw the same thing happen in development but never in production (using custom SQL storage).

janklimo avatar Mar 08 '21 16:03 janklimo

I am getting similar errors (not using ngrok, but live online app).

Nearly every time I reload the app it goes through Oauth, and sometimes I get this error: Cannot complete OAuth process. No session found for the specified shop url:

I understand this is probably because the session is not storing or loading properly. I've followed the Node tutorial here: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react

I assumed the packages took care of the auth, but it doesn't work consistently even with SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),

The docs also say that is not a good method to use for production, and to use a custom method. But it does not say how to do that, other than a few incomplete lines of code without much explanation. I'm not sure why the tutorial would say "Here, do it like this for now. But change it to something else later, we just won't show you what."

Does anyone know how to implement "proper" session storage for a production app? For what it's worth, my app does not need Shopify to authenticate with the backend, since it has its own auth system. But it's still necessary to connect to Shopify.

Server.js file below:

require('isomorphic-fetch');
const dotenv = require('dotenv');
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth');
const { verifyRequest } = require('@shopify/koa-shopify-auth');
const { default: Shopify, ApiVersion, SessionStorage } = require('@shopify/shopify-api');
const Router = require('koa-router');
const axios = require('axios').default;

dotenv.config();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SHOPIFY_API_SCOPES.split(","),
  HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/https:\/\//, ""),
  API_VERSION: '2021-01',
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(() => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(
    createShopifyAuth({
      accessMode: 'online',
      async afterAuth(ctx) {
        const { shop, accessToken, scope } = ctx.state.shopify;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        //Store accessToken in database

        ctx.cookies.set('shopOrigin', shop, {
          httpOnly: false,
          secure: true,
          sameSite: 'none'
        });
        ctx.redirect(`/?shop=${shop}`);
      },
    }),
  );

  router.post("/graphql", verifyRequest(), async (ctx, next) => {
    await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
  });

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  router.get("/", async (ctx) => {
    const shop = ctx.query.shop;

    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });

  router.get("(/_next/static/.*)", handleRequest);
  router.get("/_next/webpack-hmr", handleRequest);
  router.get("(.*)", verifyRequest(), handleRequest);

  server.use(router.allowedMethods());
  server.use(router.routes());

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

jt274 avatar Mar 08 '21 19:03 jt274

Hey @mirceapiturca, I think all you're lacking is to change the line

router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions

to

router.get("(.*)", verifyRequest({accessMode: 'offline'}), handleRequest); // Everything else must have sessions

Your app is trying to load an online session on requests, but it's performing OAuth for offline sessions.

Thank you for your support @paulomarg. Indeed I've missed adding the offline mode to verifyRequest but I am still getting those errors. For some reason, that initial cookie that creates the session is not passed correctly on my setup.

Will update if I find a solution for this.

Thank you again

mirceapiturca avatar Mar 08 '21 19:03 mirceapiturca

If it helps here is my setup:

Shopify APP CLI version: 1.6.0 Node version: v12.16.3 Storage: MemorySessionStorage Localhost using Ngrock Fresh install, only change made was adding accessMode: "offline" in createShopifyAuth and verifyRequest({accessMode: "offline"}).

Thank you

mirceapiturca avatar Mar 08 '21 20:03 mirceapiturca

image

Facing the same issue after upgrading to 4.0.2 as mentioned by @mirceapiturca . accessMode set as offline in verifyRequest.

In my case, the app never loads successfully. I consistently get this cookie error whenever I select this app from the available apps list.

During installation, I get the OAuth error - "Cannot complete OAuth process. No session found for the specified shop url: mystorename.myshopify.com"

harishannam avatar Mar 09 '21 12:03 harishannam

@janklimo I was running the example app as offline. Sessions are being stored correctly and loaded correctly. I looked into Shopify node API and found this error is a bit misleading or at least there is overlap in the term "session". It is being thrown when a cookie is missing. I removed the calls to cookies and no longer experience the error.

https://github.com/Shopify/shopify-node-api/blob/fe5ed5475ef4cf23d7187c33c15eafeba26d9043/src/auth/oauth/oauth.ts#L111

mmccall10 avatar Mar 10 '21 04:03 mmccall10

That's a great find @mmccall10! I tested this myself ~15 times but unfortunately I couldn't reproduce the error. We'll improve that code to throw a different error on a missing cookie, though.

You did raise a great point about cookies - browsers have been adding restrictions on 3rd party cookies (cookies within iframes), so embedded apps should assume that cookies can't be used (except for OAuth which needs to happen at the top level, outside the iframe).

Note: As @janklimo pointed out when the issue was opened, the example app in https://github.com/Shopify/koa-shopify-auth wasn't working. We've since updated it to fit the new pattern. If anyone is using the previous iteration, you should check out the new one.

Unfortunately, at this point there is not much more we can do, since the package code seems to be working. If anyone finds problems in our code like the above or a specific set of actions that triggers the issue, we can investigate more.

paulomarg avatar Mar 10 '21 15:03 paulomarg

@harishannam Hey, did You find the solution for this issue. Now I am facing the same. I am sending session token in headers from the client side, but it doesn't help

vasiastep avatar Mar 10 '21 17:03 vasiastep

@vasiastep "The app could not be loaded" issue was solved by adding this - https://github.com/Shopify/shopify-app-node/blob/tutorial_fetch_data_with_apollo/server.js#L55

Still facing the OAuth error consistently. No idea how to get that fixed.

harishannam avatar Mar 10 '21 17:03 harishannam

@paulomarg I have added accessMode: offline to the Fetch data with Apollo branch. Adding this accessMode makes the GraphQL calls to fail with an error Error: Cannot proxy query. No session found.

https://github.com/harishannam/shopify-app-node/commit/462c128a9e1de9fe309c1d66936c92cfd221266c

harishannam avatar Mar 11 '21 00:03 harishannam

Same thing as soon as I get to verifyRequest it throws to oauth dance but I am missing store.myshopify.com https://admin/oauth/authorize?client_id=..............

    server.use(verifyRequest({ accessMode: 'offline' }));

    // billing routes - deprecated - https://shopify.dev/concepts/about-apis/versioning/release-notes/2021-01
    server.use(billingRouter.routes());
    server.use(billingRouter.allowedMethods());

devopsangel avatar Mar 11 '21 04:03 devopsangel

@paulomarg I am also experiencing an issue where my app does not load other pages. I was wondering, the line ACTIVE_SHOPIFY_SHOPS[shop] === undefined does not verify any user session. So if some shop installs the app, then anyone can navigate to myapp.com?shop=<some shop>. Is there something more to do there?

Other routes behind verifyRequest aren't working for me, I define them like so, and I made sure that accessMode is offline everywhere:

router.get('anotherpage', verifyRequest({accessMode: 'offline'}), handleRequest)

Could being in a development store & development app have something to do with it? The redirect after installation doesn't put my app into an iframe, it just goes to the app's page.

Also, when I poke around at the values, I find that the following return null or undefined:

const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res, false)
ctx.request.headers.authorization

If my ctx.request.headers.authorization is typically undefined, how do I set it? Even verifyRequest eventually reaches:

https://github.com/Shopify/shopify-node-api/blob/617b7c55e7f3d6400931ee95de7a14435c870083/src/auth/oauth/oauth.ts#L240

jin-ding-polymatiks avatar Mar 21 '21 05:03 jin-ding-polymatiks

I have the same issue , online mode works great but not in offline mode :/

Error: Cannot proxy query. No session found.

other people seems to have the same issue : https://community.shopify.com/c/Shopify-APIs-SDKs/Offline-Access-Token-will-cause-session-not-found/m-p/1106973#M64329

psppro26 avatar Mar 21 '21 13:03 psppro26

Hey everyone, let me see if I can answer some questions:

The GraphQL proxy is actually not supposed to be used with offline tokens, because users shouldn't be able to run queries directly using offline tokens - you could either use online tokens to use the proxy, or define your GraphQL queries in your app's backend.

@jin-ding-polymatiks you're right, ACTIVE_SHOPIFY_SHOPS doesn't do any validation, it just checks whether the user needs to install the app. There's been a bit of discussion on loading pages in a different issue - that one was about navigation within the Admin, but it applies any time you need to load a server-rendered page.

The authorization header will only be set if you use App Bridge's authenticatedFetch (example from our tutorial), which means your app should do the following when a page is loaded:

  1. Use an unauthenticated endpoint (like / in the example app) to load a page skeleton
  2. Build the App Bridge app in the skeleton page
  3. Use that app to make calls using authenticatedFetch(app) as per the example, which can be authenticated with verifyRequest

Hope this helps!

paulomarg avatar Mar 24 '21 14:03 paulomarg

Hey everyone, let me see if I can answer some questions:

The GraphQL proxy is actually not supposed to be used with offline tokens, because users shouldn't be able to run queries directly using offline tokens - you could either use online tokens to use the proxy, or define your GraphQL queries in your app's backend.

@jin-ding-polymatiks you're right, ACTIVE_SHOPIFY_SHOPS doesn't do any validation, it just checks whether the user needs to install the app. There's been a bit of discussion on loading pages in a different issue - that one was about navigation within the Admin, but it applies any time you need to load a server-rendered page.

The authorization header will only be set if you use App Bridge's authenticatedFetch (example from our tutorial), which means your app should do the following when a page is loaded:

  1. Use an unauthenticated endpoint (like / in the example app) to load a page skeleton
  2. Build the App Bridge app in the skeleton page
  3. Use that app to make calls using authenticatedFetch(app) as per the example, which can be authenticated with verifyRequest

Hope this helps!

Thanks for your answer :) So based on that. What will be the best way to get offline token on install , then get online token for every graphql request ?

cause authenticatedFetch will take the offline token if I choose offline token in Koa auth ?

Can you provide an example based on the official react app on GitHub ? Thanks

psppro26 avatar Mar 24 '21 14:03 psppro26

Hi @paulomarg. Thanks for answering these questions!

I'm facing the same issue as @harishannam and @psppro26. If offline tokens cannot be used to GraphQL queries, what is the right way to get the offline token at install and online token for each auth?

My use case is I need to get an offline token to make storefront API calls.

rahulmadduluri avatar Mar 24 '21 22:03 rahulmadduluri

For both offline and online tokens, you can see here https://github.com/Shopify/koa-shopify-auth/issues/64#issuecomment-799409035

I am experiencing some issues properly loading the online token after auth though, but it has generated both tokens successfully.

jt274 avatar Mar 24 '21 22:03 jt274

Thanks @jt274, couldn't have said it better myself - the library can work with both offline and online tokens at the same time, they will be different sessions.

Just to clarify, you can use offline tokens to make GraphQL queries, you just shouldn't use the proxy for that - your app's backend can define the queries to run instead of taking them from the client side.

Usually, since offline tokens are used in background tasks, like webhook handling / periodic jobs, you can load them using loadOfflineSession(shop) - note that this method doesn't run any authentication checks by itself, so you should make sure the shop comes from a secure location and not from user input.

paulomarg avatar Mar 25 '21 14:03 paulomarg

Thanks @jt274! The solution in that commented thread seems to have worked for me.

rahulmadduluri avatar Mar 25 '21 21:03 rahulmadduluri

Thanks @jt274! The solution in that commented thread seems to have worked for me.

Hey @rahulmadduluri, I can't seem to figure out how to make the solution that @jt274 commented to work. Would you mind sharing a more detailed example of the code for it?

nandezer avatar Apr 08 '21 15:04 nandezer

Hey @janklimo, the problem here is that you are calling verifyRequest for the / endpoint (and for static pages). With the new session token authentication, your app should always load a page skeleton without expecting a session, so it can build the App Bridge client and set up the JWT session.

It's important to note that you should not include any sensitive information in that initial request since it needs to be unauthenticated.

The React + Node tutorial has been updated to follow this new pattern (in particular, steps 5-7).

Let me know if that helps unblock you, and we can mention that in the migration guide as well! In any case, we should also remove koa-session from the example app since it's no longer needed, and make sure that it follows the new pattern.

The tutorial is changed it seems!

sagar-ranglani avatar Sep 16 '21 08:09 sagar-ranglani

@harishannam I am having the same strange behavior in my app. What was your solution? I tried everything posted in this thread but nothing works image

XamHans avatar Feb 01 '22 19:02 XamHans