Our Partner & Developer boards on the community are moving to a brand new home: the .dev community forums! While you can still access past discussions here, for all your future app and storefront building questions, head over to the new forums.

Re: How can I get an offline access token for my custom app to store securely for further use?

How can I get an offline access token for my custom app to store securely for further use?

rousnay
Shopify Partner
5 0 1

I already have built a custom app using the latest Node template (https://github.com/Shopify/shopify-app-template-node). I installed it in the development store and distributed it in another store. Now I am in need of an offline access token to use Shopify admin API.

Please guide me in obtaining an offline access token for my custom app; I will securely store it for usage.

Thanks in advance 🙂

Replies 22 (22)

SBD_
Shopify Staff
1831 273 423

Hey @rousnay,

To perform an offline task (like processing a webhook), you can do something like:

 

async function someOfflineProcess() {
  const sessionId = await shopify.api.session.getOfflineId(shop)
  const session = await shopify.config.sessionStorage.loadSession(sessionId);
  const client = new shopify.api.clients.Rest({session});

  // Use the client, e.g. update a product:
  const products = await client.put({
    path: `products/${product.id}.json`,
    data,
  });
}

 

Let me know if you get stuck!

Scott | Developer Advocate @ Shopify 

rousnay
Shopify Partner
5 0 1

@SBD_ Thank you so much for your reply, yes I am trying to process a webhook (fulfillment notification) from another server.

I tried with the code above, but giving an error in 'shop' as an argument, I tried adding the shop URL directly (
"iplaysafe-consultancy-app-store.myshopify.com") as an argument, but it's not working, (InvalidShopError: Received invalid shop argument).


How can I make it work?

Thanks again 🙂
SBD_
Shopify Staff
1831 273 423

🤔 That should do it. Can I please see your package.json?

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

const sessionId = await shopify.api.session.getOfflineId(shop) how to get shop here?

SBD_
Shopify Staff
1831 273 423

Hey @FaizaBashir 

 

`shop` is the myshopify url, for example:

const sessionId = await shopify.api.session.getOfflineId('example.myshopify.com')

 

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

thank you for your response. How to get my shopify url. I am struggling with getting shop url and access token I tried many solutions but nothing seems to work. I am working on Shopify app with node and react following documentation. How to get access token and shop url in index.js or middleware?

FaizaBashir
Shopify Partner
39 0 3

Thanks a lot. Following the solution you provided, I have successfully got access token. But I hardcoded the shop url. How to get shop url?

SBD_
Shopify Staff
1831 273 423

Given a webhook is triggering this, you could pull the shop from the webhook headers (`X-Shopify-Shop-Domain`). Be sure to verify the webhook before trusting the value.

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

Thanks a million. I am new to shopify app develpment and I am wrking on a test app for learning. No, its noot being triegered by webhook. Can you please share example of getting shop url both with and without webhook?

SBD_
Shopify Staff
1831 273 423

Hey @FaizaBashir 

 

Our wires might be getting crosses - can you please provide a description of the app you're building / what you need the token for?

 

Also be sure to check out the tutorial to familiarize yourself with the Shopify App concepts: https://shopify.dev/docs/apps/getting-started/build-app-example

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

@SBD_ I am working on a test wishlist app with node and react for learning. I am using script tag api. I am able to get access token but how to get shop url here?

await axios.post({shop}`/admin/api/`
LATEST_API_VERSION
`/script_tags.json`;
, scriptTagBody, { headers: shopifyHeader(token) })
          .then(response => { console.log(response); })
          .catch(error => console.log(error));
SBD_
Shopify Staff
1831 273 423

The shop can be obtained from the session:

 

 

app.post("/api/install", async (_req, res) => {
  console.log(res.locals.shopify.session.shop);
});

 

 

The app template comes with REST and GraphQL clients, so you don't need to manually construct the requests with Axios. Here's an example of creating a script tag with the REST client:

 

 

app.post("/api/install", async (_req, res) => {
  const client = new shopify.api.clients.Rest({session: res.locals.shopify.session});

  await client.post({
    path: "script_tags",
    data: {"script_tag":{"event":"onload","src":"https://some-example.com/script.js"}},
  });

  ... 

});

 

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

@SBD_ thanks a million. I tried the code you provided but its not being called. I am using express. I tried both in index.js and middleware but this is not executed.

const app = express();
app.post("/api/install", async (_req, res) => {
  console.log(res.locals.shopify.session.shop);
});
SBD_
Shopify Staff
1831 273 423

Hey @FaizaBashir 

 

That's just an example route. You'll need something on the app's frontend (like a 'Install tags" button) to hit the route with a fetch.

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

hey @SBD_ is there no way we could call a route in backend? if so how can we call a route in backend pleae share eample.

SBD_
Shopify Staff
1831 273 423

To do it automatically, you could run the logic after authentication, something like:

 

app.get(
  shopify.config.auth.callbackPath,
  shopify.auth.callback(),
  installScriptTags(),
  shopify.redirectToShopifyOrAppRoot()
);

 

 

Scott | Developer Advocate @ Shopify 

FaizaBashir
Shopify Partner
39 0 3

here is my index.js code

import  scriptTags from './middleware/scriptTags.js';
import shopify from "./shopify.js";

const app = express();

app.post("/api/install", async (_req, res) => {
  console.log(res.locals.shopify.session.shop);
});

const sessionId= await shopify.api.session.getOfflineId('test.myshopify.com');
const session= await shopify.config.sessionStorage.loadSession(sessionId);
const client = new shopify.api.clients.Rest({session});
console.log(client)
const USE_ONLINE_TOKENS = false;

const result=scriptTags(app);

const PORT = parseInt(process.env.BACKEND_PORT || process.env.PORT, 10);

// TODO: There should be provided by env vars
const DEV_INDEX_PATH = `${process.cwd()}/frontend/`;
const PROD_INDEX_PATH = `${process.cwd()}/frontend/dist/`;

const DB_PATH = `${process.cwd()}/database.sqlite`;

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?:\/\//, ""),
  HOST_SCHEME: process.env.HOST.split("://")[0],
  API_VERSION: LATEST_API_VERSION,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  // See note below regarding using CustomSessionStorage with this template.
  SESSION_STORAGE: new Shopify.Session.SQLiteSessionStorage(DB_PATH),
  ...(process.env.SHOP_CUSTOM_DOMAIN && {CUSTOM_SHOP_DOMAINS: [process.env.SHOP_CUSTOM_DOMAIN]}),
});

// NOTE: If you choose to implement your own storage strategy using
// Shopify.Session.CustomSessionStorage, you MUST implement the optional
// findSessionsByShopCallback and deleteSessionsCallback methods.  These are
// required for the app_installations.js component in this template to
// work properly.

Shopify.Webhooks.Registry.addHandler("APP_UNINSTALLED", {
  path: "/api/webhooks",
  webhookHandler: async (_topic, shop, _body) => {
    await AppInstallations.delete(shop);
  },
});

// The transactions with Shopify will always be marked as test transactions, unless NODE_ENV is production.
// See the ensureBilling helper to learn more about billing in this template.
const BILLING_SETTINGS = {
  required: false,
  // This is an example configuration that would do a one-time charge for $5 (only USD is currently supported)
  // chargeName: "My Shopify One-Time Charge",
  // amount: 5.0,
  // currencyCode: "USD",
  // interval: BillingInterval.OneTime,
};

// This sets up the mandatory GDPR webhooks. You’ll need to fill in the endpoint
// in the “GDPR mandatory webhooks” section in the “App setup” tab, and customize
// the code when you store customer data.
//
// More details can be found on shopify.dev:
setupGDPRWebHooks("/api/webhooks");

// export for test use only
export async function createServer(
  root = process.cwd(),
  isProd = process.env.NODE_ENV === "production",
  billingSettings = BILLING_SETTINGS
) {
 
  app.get("/electronics", function (req, res) {
    res.send("This is the electronics category");
  });
  app.get(shopify.config.auth.path, shopify.auth.begin());
  app.get(
    shopify.config.auth.callbackPath,
    shopify.auth.callback(),
    shopify.redirectToShopifyOrAppRoot()
  );

  app.set("use-online-tokens", USE_ONLINE_TOKENS);
  app.use(cookieParser(Shopify.Context.API_SECRET_KEY));

  applyAuthMiddleware(app, {
    billing: billingSettings,
  });

  // Do not call app.use(express.json()) before processing webhooks with
  // Shopify.Webhooks.Registry.process().
  // for more details.
  app.post("/api/webhooks", async (req, res) => {
    try {
      await Shopify.Webhooks.Registry.process(req, res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (e) {
      console.log(`Failed to process webhook: ${e.message}`);
      if (!res.headersSent) {
        res.status(500).send(e.message);
      }
    }
  });
  app.get("/api/shop", async (_req, res) => {
    console.log("Calling api")
      const shopData = await shopify.api.rest.Shop.all({
        session: res.locals.shopify.session,
      });
      res.status(200).send(shopData);
    });

  // All endpoints after this point will require an active session
  app.use(
    "/api/*",
    verifyRequest(app, {
      billing: billingSettings,
    })
  );

  app.get("/api/products/count", async (req, res) => {
    const session = await Shopify.Utils.loadCurrentSession(
      req,
      res,
      app.get("use-online-tokens")
    );
    const { Product } = await import(
      `@shopify/shopify-api/dist/rest-resources/${Shopify.Context.API_VERSION}/index.js`
    );

    const countData = await Product.count({ session });
    res.status(200).send(countData);
  });

  app.get("/api/products/create", async (req, res) => {
    console.log(".......................product creator")
    const session = await Shopify.Utils.loadCurrentSession(
      req,
      res,
      app.get("use-online-tokens")
    );
    let status = 200;
    let error = null;

    try {
      await productCreator(session);
    } catch (e) {
      console.log(`Failed to process products/create: ${e.message}`);
      status = 500;
      error = e.message;
    }
    res.status(status).send({ success: status === 200, error });
  });

  // All endpoints after this point will have access to a request.body
  // attribute, as a result of the express.json() middleware
  app.use(express.json());

  app.use((req, res, next) => {
    const shop = Shopify.Utils.sanitizeShop(req.query.shop);
    if (Shopify.Context.IS_EMBEDDED_APP && shop) {
      res.setHeader(
        "Content-Security-Policy",
        `frame-ancestors https://${encodeURIComponent(
          shop
        )} https://admin.shopify.com;`
      );
    } else {
      res.setHeader("Content-Security-Policy", `frame-ancestors 'none';`);
    }
    next();
  });

  if (isProd) {
    const compression = await import("compression").then(
      ({ default: fn }) => fn
    );
    const serveStatic = await import("serve-static").then(
      ({ default: fn }) => fn
    );
    app.use(compression());
    app.use(serveStatic(PROD_INDEX_PATH, { index: false }));
  }

  app.use("/*", async (req, res, next) => {
    if (typeof req.query.shop !== "string") {
      res.status(500);
      return res.send("No shop provided");
    }

    const shop = Shopify.Utils.sanitizeShop(req.query.shop);
    console.log("Shop name is ")
    console.log(shop)
    const appInstalled = await AppInstallations.includes(shop);

    if (!appInstalled && !req.originalUrl.match(/^\/exitiframe/i)) {
      return redirectToAuth(req, res, app);
    }

    if (Shopify.Context.IS_EMBEDDED_APP && req.query.embedded !== "1") {
      const embeddedUrl = Shopify.Utils.getEmbeddedAppUrl(req);

      return res.redirect(embeddedUrl + req.path);
    }

    const htmlFile = join(
      isProd ? PROD_INDEX_PATH : DEV_INDEX_PATH,
      "index.html"
    );

    return res
      .status(200)
      .set("Content-Type", "text/html")
      .send(readFileSync(htmlFile));
  });

  return { app };
}

createServer().then(({ app }) => app.listen(PORT));
FaizaBashir
Shopify Partner
39 0 3

I see this endpoint in index.js inside createServer function. How to call this in index.js or middleware? When i call this endpoint with axios i get eeror invlid url.

app.get("/api/shop", async (_req, res) => {
    const shopData = await shopify.api.rest.Shop.all({
      session: res.locals.shopify.session,
    });
    res.status(200).send(shopData);
  });
FaizaBashir
Shopify Partner
39 0 3

Could you please help me. I searched alot but couldnt find solution. How i get shop url? I am stuck please help

FaizaBashir
Shopify Partner
39 0 3

Hey, @SBD_ I am waiting for your response,please help me. How to get shop url?

FaizaBashir
Shopify Partner
39 0 3

Could you please help me?  How to get shop url in backend? in index.js or middleware. I want to call script tag api how to get shop here 

how did you get shop here await shopify.api.session.getOfflineId(shop)

FaizaBashir
Shopify Partner
39 0 3

Hey @rousnay, Could you please help me. How to get shop url?