question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Custom session storage generates multiple session IDs from single app install

See original GitHub issue

I’m not 100% sure this is a bug, but it seems to be unexpected behaviour so I need to ask…

I built a Node shopify app using the shopify CLI (recent version).

In my server.js file, I’m initialising the Shopify context to use custom session storage, like so:

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,
  SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    storeCallback,
    loadCallback,
    deleteCallback
  ),
});

I used the guide to define the 3 required callback methods. I’m using Firebase for my custom storage solution, so my code looks like this:

// https://github.com/Shopify/shopify-node-api/blob/main/docs/usage/customsessions.md

/*
  The storeCallback takes in the Session, and sets it in Firestore
  This callback is used for BOTH saving new Sessions and updating existing Sessions
  Returns a Firebase write result if the session can be stored
*/
const storeCallback = async (session) => {
  console.log(
    `Custom session storage storeCallback fired with id [${session.id}]`
  );
  try {
    await db
      .doc(`app-sessions/${session.id}`)
      .set(JSON.parse(JSON.stringify(session)), { merge: true });
    return true;
  } catch (err) {
    throw new Error(err);
  }
};

/*
  The loadCallback takes in the id, and tries to retrieve the session data from Firestore
  If a stored session exists, it's returned
  Otherwise, return undefined
  */
const loadCallback = async (id) => {
  console.log(`Custom session storage loadCallback fired with id [${id}]`);
  try {
    const sessionSnapshot = await db
      .doc(`app-sessions/${id}`)
      .get();
    if (!sessionSnapshot.exists) {
      console.log(`Custom session storage session id [${id}] does not exist`);
      return undefined;
    }
    const session = sessionSnapshot.data();
    if (!session) {
      console.log(`Custom session storage session id [${id}] no data`);
      return undefined;
    }
    return session;
  } catch (err) {
    throw new Error(err);
  }
};

/*
    The deleteCallback takes in the id, and attempts to delete the session from Firestore
    If the session can be deleted, return true,
    otherwise, return false
  */
const deleteCallback = async (id) => {
  console.log(`Custom session storage deleteCallback fired with id [${id}]`);
  try {
    const sessionSnapshot = await db
      .doc(`app-sessions/${id}`)
      .get();
    if (!sessionSnapshot.exists) {
      console.log(`Custom session storage session id [${id}] does not exist`);
      return false;
    }
    await db.doc(`app-sessions/${id}`).delete();
    return true;
  } catch (err) {
    throw new Error(err);
  }
};

What I would expect to see when a user installs the app is a single session object being stored in the DB.

What I actually see is 2 session objects being stored, each with a different session ID.

Here is the relevant portion of my logs from a single app install on my development store at the point where the custom session methods are being executed:

2021-07-28T16:36:38.847238+00:00 app[web.1]: Custom session storage loadCallback fired with id [3b8ecc7b-2f8b-401e-9ef0-dc4dee1a0c6c]
2021-07-28T16:36:39.399084+00:00 app[web.1]: Custom session storage storeCallback fired with id [my-test-store.myshopify.com_75101241518]
2021-07-28T16:36:39.503989+00:00 app[web.1]: Custom session storage storeCallback fired with id [3b8ecc7b-2f8b-401e-9ef0-dc4dee1a0c6c]
2021-07-28T16:36:39.602216+00:00 app[web.1]: Custom session storage loadCallback fired with id [3b8ecc7b-2f8b-401e-9ef0-dc4dee1a0c6c]
2021-07-28T16:36:39.696272+00:00 app[web.1]: after auth...

Note:

  • Both session objects have an identical session.accessToken and session.onlineAccessInfo.associatedUser.session, but different session.id and session.expires value.

So my questions:

  1. Why is storeCallback firing twice, with 2 different session IDs even though as a user, I’ve only started one session from one app install?
  2. When a user loads the app, I’m planning on inspecting the session.expires to decide whether to send the user back into the oauth flow (I think that is the correct implementation but stop me if I’m wrong). With 2 different sessions for the same user, which one should I inspect? I’ve tested locally and ended up with 4 session objects from the same app/install, which is really odd! I’m guessing that if I answer question 1, question 2 becomes irrelevant.

Thanks in advance for the help 😃

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:13
  • Comments:27 (2 by maintainers)

github_iconTop GitHub Comments

10reactions
thecodepixicommented, Jul 28, 2021

Hey folks! Thanks for raising this. This is a know issue we’re currently working on. Sorry we don’t have an immediate solution to suggest, but hopefully this bug will be gone soon.

7reactions
chamathdevcommented, Aug 4, 2021

Hi, I’m also having the same issue that getting two sessions, When I save it in the MongoDB with NodeJS it will record 2 sessions. And someone can explain how deleteCallback and loadCallback work when those functions fire?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Custom session storage generates multiple session IDs from ...
Custom session storage generates multiple session IDs from single app install #224 ... I've only started one session from one app install?
Read more >
Tutorial Custom Session Storage Shopify Node App and 403 ...
Would you like to be ready to start applying to jobs in 3 to 6 weeks? Check the tech accelerator program free training...
Read more >
Best Practices for Secure Session Management in Node
Learn the best practices to secure sessions in your app. ... Additionally, a server can accept session identifiers by multiple means.
Read more >
Rails app with different session store for each model
A solution will be creating some custom model which enumerates Admin user sessions, and is maintained by the app. This is simple enough...
Read more >
Session Management - OWASP Cheat Sheet Series
Web applications can create sessions to keep track of anonymous users after the very first user request. An example would be maintaining the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found