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.

We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more

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?