Solved

Authenticating customers in theme app extension

wristbands
Shopify Partner
20 3 8

Hey so we are developing a theme app extension, which creates an app block that will be used for customers to enter in additional account information. Our app block will send a request containing the additional information to our backend app server, which will use the Shopify Admin API to insert the data on behalf of the customer.

 

However, our problem is we want to make sure that each customer can only add additional information for their own account, not for other customers' accounts. To do this, we need some way of authenticating each customer in their request. Is there some way of accessing a customer's session token and verifying that it is valid in our backend app server? Would Access tokens for the Storefront API be relevant?

 

Thanks,
Elias

Accepted Solution (1)

wristbands
Shopify Partner
20 3 8

This is an accepted solution.

I was able to figure it out. You have to use App Proxies.

 

Once you set that up, you can determine the ID of the logged in user who sent the request by reading the `logged_in_customer_id` query parameter, and you can make sure that the request came from Shopify by verifying the `signature` query parameter. For a Node.js app, you can verify the signature using the shopify-application-proxy-verification npm library.

 

Hope that helps anyone in a similar situation!

View solution in original post

Replies 8 (8)

EcomGraduates
Shopify Partner
588 48 79

In order to authenticate each customer and ensure they can only add additional information for their own account, you can make use of Shopify's API authentication features.

Specifically, you can use the Shopify Admin API to obtain a session token for each customer when they log in. This token can then be passed along with each request to your backend app server, which can verify the token's validity and use it to perform actions on behalf of the customer.

Access tokens for the Storefront API would not be relevant in this case, as they are used for accessing storefront data rather than authenticating customer requests.

 

abishekrs
Shopify Partner
15 3 7

Can you please elaborate a bit more on this as to how to obtain the session token for the current customer on the Online Store?

 

wristbands
Shopify Partner
20 3 8

This is an accepted solution.

I was able to figure it out. You have to use App Proxies.

 

Once you set that up, you can determine the ID of the logged in user who sent the request by reading the `logged_in_customer_id` query parameter, and you can make sure that the request came from Shopify by verifying the `signature` query parameter. For a Node.js app, you can verify the signature using the shopify-application-proxy-verification npm library.

 

Hope that helps anyone in a similar situation!

anhdd-kuro-159
Shopify Partner
14 0 5

@wristbands 

Could you please provide an example code?

Although I attempted to implement the solution you suggested but is's not going well

wristbands
Shopify Partner
20 3 8

Sure, first you need to follow the instructions in Add an app proxy.

 

Then when processing the request in your Shopify app code inside web/index.js, you can do the signature validation and access the customer ID like:

 

/* You'll need to first install the shopify-application-proxy-verification npm library for this import statement to work. */
import { verifyAppProxyHmac as queryHasValidSignature } from 'shopify-application-proxy-verification';

/* The existing code in index.js will be here. */

/* Process requests coming to any endpoint of your choosing. */
app.all("/your/endpoint", async (req, res) => {
  if (!queryHasValidSignature(req.query, process.env.SHOPIFY_API_SECRET)) {
    return res.status(403).json({ errorMessage: 'Request was not correctly signed by Shopify' });
  }

  const customerId = req.query.logged_in_customer_id
  if (customerId?.length === 0) {
    const errorMessage = 'Please log in before hitting endpoint'
    return res.status(401).json({ errorMessage });
  });

  /* Then, continue processing the request as needed. */
})

 

 

 

anhdd-kuro-159
Shopify Partner
14 0 5

@wristbands thanks you but it's still not working , could you please take a look at my setting

Did I miss something ?

 

APP URL : https://depending-flip-exposure-popular.trycloudflare.com/ 
( just some random when run yarn dev )

Prefix: apps

Sub path: kuro-proxy

Proxy URL : https://depending-flip-exposure-popular.trycloudflare.com/

 

Here is my code

 

// index.ts
// @ts-check
import { join } from "path";
import { readFileSync } from "fs";
import express, { NextFunction, Request, Response } from "express";
import serveStatic from "serve-static";

import shopify from "./shopify";
import GDPRWebhookHandlers from "./gdpr";
import {verifyAppProxyHmac} from "./utils";

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

const STATIC_PATH =
  process.env.NODE_ENV === "production"
    ? `${process.cwd()}/frontend/dist`
    : `${process.cwd()}/frontend/`;

const app = express();

// Set up Shopify authentication and webhook handling
app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(
  shopify.config.auth.callbackPath,
  shopify.auth.callback(),
  shopify.redirectToShopifyOrAppRoot()
);
app.post(
  shopify.config.webhooks.path,
  shopify.processWebhooks({ webhookHandlers: GDPRWebhookHandlers })
);

app.use("/api/*", shopify.validateAuthenticatedSession());

app.use(express.json());

app.all("/proxy/test", async (req, res) => {
  console.log("test test")
  if (!verifyAppProxyHmac(req.query, process.env.SHOPIFY_API_SECRET)) {
    return res.status(403).json({ errorMessage: 'Request was not correctly signed by Shopify' });
  }

  const customerId = req.query.logged_in_customer_id
  if (customerId?.length === 0) {
    const errorMessage = 'Please log in before hitting endpoint'
    return res.status(401).json({ errorMessage });
  }

  /* Then, continue processing the request as needed. */
})

app.use(serveStatic(STATIC_PATH, { index: false }));

app.use("/*", shopify.ensureInstalledOnShop(), async (_req, res, _next) => {
  return res
    .status(200)
    .set("Content-Type", "text/html")
    .send(readFileSync(join(STATIC_PATH, "index.html")));
});

app.listen(PORT);

 

Here is the block in theme app ext

 

<script>
  const result = fetch('/apps/kuro-proxy/proxy/test', {
    headers: {
      'Content-Type': 'application/json',
    },
  }).then((response) => {
    console.log(response);
  });
</script>

{% schema %}
{
  "name": "Test",
  "target": "section",
}
{% endschema %}

 

Here is the request

https://xxx.myshopify.com/apps/kuro-proxy/proxy/test

the response is /web/front-end/index.html , it don't even run into /proxy/test

What am I missing here ?

wristbands
Shopify Partner
20 3 8

Yeah your code does appear correct. So it seems your problem isn't signature verification at all but just getting the server to respond to any requests coming from your app blocks.

 

I notice you use the fetch method to automatically construct the url from the base url of the page, but in my code I manually constructed the url myself. Maybe you could try that? Like here's how I generated my app base url, which you can try using in your fetch method (you'd just add on the "/proxy/test" to the the URL returned from `makeAppBaseUrl()`)

<div id="shared" data-shop-domain="{{ shop.domain }}"></div>
<script>
function makeBaseUrl () {
  const shopDomain = document.getElementById("shared").dataset.shopDomain
  const baseUrlString = 'https://' + shopDomain;
  return new URL(baseUrlString)
}

function makeAppBaseUrl () {
  const baseUrl = makeBaseUrl()
  return new URL('/apps/kuro-proxy/', baseUrl)
}
</script>

 

Sorry I won't have much more time to help you, but I'm sure you can get it to work! It might just take some more debugging to figure out where the problem point is.

vivekagarwal
Shopify Partner
1 0 0

https://www.youtube.com/watch?v=WfxJhAOD2tg

Follow this tutorial. This is very helpful