Solved

Shopify-api-node get both offline and online token

nmats
Shopify Partner
2 0 0

Hello.

 

I'm a new to Shopify app development and trying to get both offline and online token by making a custom app which is made by the cli command "shopify app create node".

 

In this app, since I would like to use both Admin and Storefront API, I want to get both offline and online token during the auth process. However, I can't really understand how could I do that as I can't find any reference for that process.

 

Appreciate if anybody has solution for this.

 

Thanks!

Accepted Solution (1)
visidea
Shopify Partner
4 1 4

This is an accepted solution.

Hi @buildpath, that's the way we implemented it in NodeJS + MongoDB:

 

 

  app.get("/auth/callback", async (req, res) => {
    try {
      const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

      // save online token (session.accessToken)
      Website.countDocuments({ shop: session.shop }, function (err, count) {
        if (count == 0) {
          var website = {
            id: session.id,
            shop: session.shop,
            access_token: session.accessToken,
            creation: Date.now(),
            modified: Date.now(),
          };

          Website.create(website, function (err, doc) {
            if (err) {
              console.log("Something wrong when creating data!");
              console.log(err);
            }
          });
        }
      });

      // register uninstall webhook
      const response = await Shopify.Webhooks.Registry.register({
        shop: session.shop,
        accessToken: session.accessToken,
        topic: "APP_UNINSTALLED",
        path: "/webhooks",
      });

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

      // Redirect to second callback
      const redirectUrl = await Shopify.Auth.beginAuth(
        req,
        res,
        req.query.shop,
        "/auth/callback2",
        false
      );

      res.redirect(redirectUrl);
    } catch (e) {
      switch (true) {
        case e instanceof Shopify.Errors.InvalidOAuthError:
          res.status(400);
          res.send(e.message);
          break;
        case e instanceof Shopify.Errors.CookieNotFound:
        case e instanceof Shopify.Errors.SessionNotFound:
          // This is likely because the OAuth session cookie expired before the merchant approved the request
          res.redirect(`/auth?shop=${req.query.shop}`);
          break;
        default:
          res.status(500);
          res.send(e.message);
          break;
      }
    }
  });

  app.get("/auth/callback2", async (req, res) => {
    try {
      const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

      const host = req.query.host;

      // save offline token (session.accessToken)
      await Website.findOneAndUpdate(
        { shop: session.shop },
        { offline_token: session.accessToken }
      );

      // Redirect to app with shop parameter upon auth
      res.redirect(`/?shop=${session.shop}&host=${host}`);
    } catch (e) {
      switch (true) {
        case e instanceof Shopify.Errors.InvalidOAuthError:
          res.status(400);
          res.send(e.message);
          break;
        case e instanceof Shopify.Errors.CookieNotFound:
        case e instanceof Shopify.Errors.SessionNotFound:
          // This is likely because the OAuth session cookie expired before the merchant approved the request
          res.redirect(`/auth?shop=${req.query.shop}`);
          break;
        default:
          res.status(500);
          res.send(e.message);
          break;
      }
    }
  });
}

 

 

Hope it helps. Good luck.

View solution in original post

Replies 9 (9)

visidea
Shopify Partner
4 1 4

Hello Nmats, did you find a solution to get offline token? I'm trying to do the same thing but this feature is not well documented.

 

Thanks a lot!

nmats
Shopify Partner
2 0 0

Hi Visidea, unfortunately not yet.

visidea
Shopify Partner
4 1 4

FYI: we developed a solution in 2 steps, the first saves the online token, then redirects to the second endpoint. In this second step, we create the offline token and save it.

buildpath
Shopify Partner
59 11 20

@visidea can you share any of that code here? I've been searching for a couple days to find some documentation and discussion of this. 

Ecom entrepreneur since 2004 | Shopify App developer since 2021 | Shopify merchant and theme developer since 2016
visidea
Shopify Partner
4 1 4

This is an accepted solution.

Hi @buildpath, that's the way we implemented it in NodeJS + MongoDB:

 

 

  app.get("/auth/callback", async (req, res) => {
    try {
      const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

      // save online token (session.accessToken)
      Website.countDocuments({ shop: session.shop }, function (err, count) {
        if (count == 0) {
          var website = {
            id: session.id,
            shop: session.shop,
            access_token: session.accessToken,
            creation: Date.now(),
            modified: Date.now(),
          };

          Website.create(website, function (err, doc) {
            if (err) {
              console.log("Something wrong when creating data!");
              console.log(err);
            }
          });
        }
      });

      // register uninstall webhook
      const response = await Shopify.Webhooks.Registry.register({
        shop: session.shop,
        accessToken: session.accessToken,
        topic: "APP_UNINSTALLED",
        path: "/webhooks",
      });

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

      // Redirect to second callback
      const redirectUrl = await Shopify.Auth.beginAuth(
        req,
        res,
        req.query.shop,
        "/auth/callback2",
        false
      );

      res.redirect(redirectUrl);
    } catch (e) {
      switch (true) {
        case e instanceof Shopify.Errors.InvalidOAuthError:
          res.status(400);
          res.send(e.message);
          break;
        case e instanceof Shopify.Errors.CookieNotFound:
        case e instanceof Shopify.Errors.SessionNotFound:
          // This is likely because the OAuth session cookie expired before the merchant approved the request
          res.redirect(`/auth?shop=${req.query.shop}`);
          break;
        default:
          res.status(500);
          res.send(e.message);
          break;
      }
    }
  });

  app.get("/auth/callback2", async (req, res) => {
    try {
      const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

      const host = req.query.host;

      // save offline token (session.accessToken)
      await Website.findOneAndUpdate(
        { shop: session.shop },
        { offline_token: session.accessToken }
      );

      // Redirect to app with shop parameter upon auth
      res.redirect(`/?shop=${session.shop}&host=${host}`);
    } catch (e) {
      switch (true) {
        case e instanceof Shopify.Errors.InvalidOAuthError:
          res.status(400);
          res.send(e.message);
          break;
        case e instanceof Shopify.Errors.CookieNotFound:
        case e instanceof Shopify.Errors.SessionNotFound:
          // This is likely because the OAuth session cookie expired before the merchant approved the request
          res.redirect(`/auth?shop=${req.query.shop}`);
          break;
        default:
          res.status(500);
          res.send(e.message);
          break;
      }
    }
  });
}

 

 

Hope it helps. Good luck.

buildpath
Shopify Partner
59 11 20

@visidea  this is EXACTLY what I needed - thank you! I need to add an additional auth callback for the offline token, I see. Perfect.

UPDATE: I got it all working perfectly. My app is now getting both online and offline access tokens. Thanks again!

Ecom entrepreneur since 2004 | Shopify App developer since 2021 | Shopify merchant and theme developer since 2016
SilasGrygier
Shopify Partner
19 2 7

Thanks so much, this helped me setup my tokens too! I just have one question about the storing process of the offline token in your auth/callback2 route. You run a code snippet that stores the token as follows: 

  // save offline token (session.accessToken)
      await Website.findOneAndUpdate(
        { shop: session.shop },
        { offline_token: session.accessToken }
      );

 But there's a code snippet just before that which already stores the session and token for you if I'm not mistaken: 

 const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

The function Shopify.Auth.validateAuthCallback()  stores the session/token for you using your session storage handler, here's a code snippet of what the function does:

  const sessionStored = await Context.SESSION_STORAGE.storeSession(session);
    if (!sessionStored) {
      throw new ShopifyErrors.SessionStorageError(
        'Session could not be saved. Please check your session storage functionality.',
      );
    }

The entire function implementation of validateAuthCallback

 

So what I'm asking is why is there a need to manually store the tokens in your auth functions? I'm very new to shopify so I could have overlooked something.

 

visidea
Shopify Partner
4 1 4

Hi @SilasGrygier,

 

I need to save the tokens for later access, for example, to access the Shopify APIs. We use MongoDB as data storage, so that's the way we implemented persistence.

If you have other business requirements and you don't need to retrieve the tokens later, you can skip that phase.

SilasGrygier
Shopify Partner
19 2 7

What I'm trying to say is that you wrote 2 code snippets that are doing the same thing:

 

// save offline token (session.accessToken)
      await Website.findOneAndUpdate(
        { shop: session.shop },
        { offline_token: session.accessToken }
      );

 

Is doing the same thing as this:

 

 const session = await Shopify.Auth.validateAuthCallback(
        req,
        res,
        req.query
      );

 

So why include both because validateAuthCallback already stores the tokens in your database