shopify-api-js icon indicating copy to clipboard operation
shopify-api-js copied to clipboard

blocker bug: OAuth not work if session was lost on SessionStorage!

Open Lancer92 opened this issue 3 years ago • 3 comments

Also have such error. Looks like for now custom session storage must also have "optional" findSessionsByShopCallback method:

CustomSessionStorage failed to find sessions by shop. Error Details: findSessionsByShopCallback not defined.

but after I added it, it started to search for sessions in another way, so can't find them without update. It starts recursively redirect to /exitiframe, and app not loads and shows "too many redirects" error. When I added exception, not redirect to that path if it's already the same, I received just same error

CookieNotFound [Error]: Cannot complete OAuth process. Could not find an OAuth cookie for shop url: ******.myshopify.com

It's a blocker bug!

Originally posted by @Lancer92 in https://github.com/Shopify/shopify-api-node/issues/486#issuecomment-1250726892

Lancer92 avatar Sep 19 '22 08:09 Lancer92

👋🏻 What is the starting point of your app? Are you using the Shopify CLI to generate your app?

If the starting point of your app is the shopify-app-template-node (used by the CLI), and if you're then customizing it to use Custom Session Storage, then the findSessionsByShop method needs to be implemented to support the app template. (It was added to support the app template; it's optional from the API library perspective.)

The app template uses the session storage to track which stores have installed the app (i.e., which shop domains have had at least one completed login session).

mkevinosullivan avatar Sep 19 '22 18:09 mkevinosullivan

Hi Kevin, thanks for the quick answer. Yes, you right, it was shopify cli, after that it was update for the new oauth flow, and ok, it's clear now in which case findSessionsByShop is optional. But is still not clear what to do if session was removed from storage. How to generate new one? trying to

const redirectUrl = await Shopify.Auth.beginAuth(
    req,
    res,
    req.query.shop,
    "/api/auth/callback",
    app.get("use-online-tokens")
  );

results in Error

CookieNotFound [Error]: Cannot complete OAuth process. Could not find an OAuth cookie for shop url:
******.myshopify.com

Lancer92 avatar Sep 20 '22 07:09 Lancer92

@Lancer92 what version of the API library is the app using? Maybe the easiest thing to do is share the content of web/package.json, or at least the dependencies/devDependencies sections.

mkevinosullivan avatar Sep 20 '22 19:09 mkevinosullivan

  "dependencies": {
    "@shopify/shopify-api": "^5.0.1",
    "airtable": "^0.11.4",
    "compression": "^1.7.4",
    "cookie-parser": "^1.4.6",
    "cross-env": "^7.0.3",
    "express": "^4.18.1",
    "serve-static": "^1.15.0"
  },
  "devDependencies": {
    "@serverless/cloud": "^2.10.16",
    "jsonwebtoken": "^8.5.1",
    "nodemon": "^2.0.15",
    "prettier": "^2.7.1",
    "pretty-quick": "^3.1.3"
  },

Lancer92 avatar Sep 22 '22 09:09 Lancer92

@Lancer92 Are you able to provide more details (or, even better, code for a reduced app) that reproduces the error? What platforms/environments are you running this with, what version of Node, browser, etc.?

Do you see a shopify_app_session cookie in your browser when you're attempting to login/authenticate?

mkevinosullivan avatar Sep 22 '22 18:09 mkevinosullivan

I am also using the CustomSessionStorage to implement my session storage. From the example I found on the internet, they don't specifically implement findSessionsByShopCallback. Can you provide an example snippet to implement custom storage? or in order word how can I connect with monogodb?

jeff0723 avatar Sep 29 '22 13:09 jeff0723

@jeff0723 To connect wit MongoDB, there's a MongoDBSessionStorage adapter available.

https://github.com/Shopify/shopify-api-node/tree/main/src/auth/session/storage#mongodb

mkevinosullivan avatar Sep 29 '22 14:09 mkevinosullivan

I use it with Serverless Cloud Data. The problem is when sessions can't be found on SessionStorage, the new ones cant be cretead, with already mentined error about cookie. Here is my CustomSessionStorage

import { data } from '@serverless/cloud';
import { Shopify } from "@shopify/shopify-api";

/*
  The storeCallback takes in the Session, and sets a stringified version of it on the redis store
  This callback is used for BOTH saving new Sessions and updating existing Sessions.
  If the session can be stored, return true
  Otherwise, return false
*/
async function save(session = {}) {
  try {
    // Inside our try, we use the `setAsync` method to save our session.
    // This method returns a boolean (true if successful, false if not)
    return await data.set('sess:' + session.id, JSON.stringify(session), {
      label1: session.id.replace('offline_', 'shop:')
    });
  } catch (err) {
    // throw errors, and handle them gracefully in your application
    throw new Error(err);
  }
}

/*
  The loadCallback takes in the id, and uses the getAsync method to access the session data
    If a stored session exists, it's parsed and returned
    Otherwise, return undefined
*/
async function load(id = 'key') {
  try {
    let sess = await data.get('sess:' + id);

    sess &&= JSON.parse(sess)

    // Inside our try, we use `getAsync` to access the method by id
    // If we receive data back, we parse and return it
    // If not, we return `undefined`
    return sess || undefined;
  } catch (err) {
    throw new Error(err);
  }
}

/*
  The deleteCallback takes in the id, and uses the `del` method to delete it from the store
  If the session can be deleted, return true
  Otherwise, return false
*/
async function del(id = 'key') {
  try {
    // Inside our try, we use the `delAsync` method to delete our session.
    // This method returns a boolean (true if successful, false if not)
    return await data.remove('sess:' + id);
  } catch (err) {
    throw new Error(err);
  }
}

const delMany = ids => data.remove(ids.map(id => 'sess:' + id))

const findByShop = shop => {
  return data.getByLabel('label1', 'shop:' + shop).then(res => {
    return res.items.map(({ value }) => JSON.parse(value))
  })
}

export const sessionStorage = new Shopify.Session.CustomSessionStorage(
  save,
  load,
  del,
  delMany,
  findByShop
)

export default sessionStorage;

So for old records there was no indexable "label1" to be able to find all sessions by shop. I fixed It by manually adding them (data migration) But what if there was impossible implement findSessionByShop or if sessions was accidentally cleaned? App is not loading on a store, and only reinstallation helps to solve it, but only developer can investigate it

Lancer92 avatar Sep 30 '22 13:09 Lancer92

This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Nov 30 '22 02:11 github-actions[bot]

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!

github-actions[bot] avatar Jan 31 '23 02:01 github-actions[bot]