Generate search tokens with your app proxy (Shopify Liquid)
Generate search tokens with your app proxy (Shopify Liquid)
We recommend using search tokens to authenticate Coveo API requests in Shopify Liquid themes.
|
If you’re using Shopify Hydrogen, see Authenticate requests (Shopify Hydrogen). |
To generate search tokens, you have two choices:
-
If you’ve installed the Coveo AI Search & Discovery app, it includes an app proxy that generates search tokens for unauthenticated users. This is the easiest method for generating search tokens for your Shopify Liquid themes, but it provides limited flexibility.
-
If you don’t use the app—or if you need to generate search tokens for authenticated users or support additional features—create your own app proxy that generates search tokens through the Coveo Search API.
For instance, with your own search tokens, you can implement availability filtering, which lets you filter the products that users can access based on their roles or regions. You can also filter content using dictionary fields or multi-value fields.
This article explains how to generate search tokens using the Coveo Search API and a Shopify app proxy. As an example, it uses a Shopify Remix app, but you could use another framework to build your proxy or add to your existing app proxy. The same principles apply.
Prerequisites
-
If you don’t already have a Shopify app proxy, create one.
-
If you don’t already have a Shopify Admin API key with permission to read customer email addresses, create one. Store it as an environment variable in your app proxy and don’t expose it in your client-side code.
Create and store a Coveo API key
To generate tokens yourself by making Coveo Search API requests, you first need to create a secure API key that will be stored in your back-end. Use the Authenticated search API key template to generate an API key with the required permissions. We recommend storing it as an environment variable in your app proxy.
|
Don’t expose this API key in client-side code because it can be used to impersonate any user. |
Create a route to generate search tokens
There are three main building blocks to create a route that generates search tokens, as you can see in the following sample Remix route.
// app/routes/appproxy.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import crypto from "crypto";
export const loader = async ({ request }: LoaderFunctionArgs) => {
validateSignature(request);
const userName = await getCustomer(request);
const token = await fetchToken(userName);
return new Response(JSON.stringify({ token: token }), {
headers: {
'Content-Type': 'application/json',
},
});
};
// ...
Validate the request signature
Create a function that validates request signatures.
// app/routes/appproxy.tsx
// ...
export const validateSignature = (request: Request) => {
const url = new URL(request.url);
const hmac = url.searchParams.get("signature");
const queryParams = new URLSearchParams(url.searchParams);
queryParams.delete("signature");
const sortedParams = Array.from(queryParams.entries())
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
.join("");
const clientSecret = process.env.CLIENT_SECRET;
if (!clientSecret) {
return new Response(JSON.stringify({ error: "Missing client secret" }), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
const generatedHmac = crypto
.createHmac("sha256", clientSecret)
.update(sortedParams)
.digest("hex");
if (generatedHmac !== hmac) {
return new Response(JSON.stringify({ error: "Invalid signature" }), {
status: 403,
headers: {
'Content-Type': 'application/json',
},
});
};
return;
};
The client secret of the Shopify app in which you created the app proxy. | |
Return a 403 error if the signature is invalid. |
Fetch the customer email address
Create a function that fetches the customer’s email address from the request by using the logged_in_customer_id
search parameter.
// app/routes/appproxy.tsx
// ...
export const getCustomer = async (request: Request) => {
const url = new URL(request.url);
const loggedInCustomerId = url.searchParams.get("logged_in_customer_id");
if (!loggedInCustomerId) {
return "anonymous";
}
const shop = process.env.SHOPIFY_SHOP;
const accessToken = process.env.ADMIN_API_ACCESS_TOKEN;
if (!shop || !accessToken) {
throw new Error("Missing SHOPIFY_SHOP or ADMIN_API_ACCESS_TOKEN environment variables");
}
const adminUrl = `https://${shop}/admin/api/2023-04/customers/${loggedInCustomerId}.json`;
const response = await fetch(adminUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
});
if (!response.ok) {
throw new Error(`Failed to fetch customer data: ${response.statusText}`);
}
const data = await response.json();
return data.customer?.email;
};
Return anonymous if the user isn’t logged in. |
|
The Shopify store URL.
For example, your-store.myshopify.com . |
|
The Shopify Admin API access token with the privilege to read customer email addresses. |
Generate the search token
Create a function that generates a search token using the Coveo Search API.
// app/routes/appproxy.tsx
// ...
export const fetchToken = async (username: string) => {
const coveoTokenEndpoint = `https://${process.env.COVEO_ORG_ID}.org.coveo.com/rest/search/v2/token`
const response = await fetch(coveoTokenEndpoint, {
method: 'POST',
body: JSON.stringify({
userIds: [
{
name: username,
type: 'User',
provider: 'Email Security Provider',
infos: {},
authCookie: '',
},
],
}),
headers: {
Authorization: `Bearer ${process.env.COVEO_API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
console.error('Error:', response.statusText);
throw new Error('Failed to fetch access token from Coveo Search API');
}
const responseData = (await response.json()) as {token: string};
return responseData.token;
}
The COVEO_ORG_ID environment variable stores your organization ID.
See Find your Coveo organization ID. |
|
For the list of possible request body properties, see Use search token authentication: Request body properties. | |
The Coveo API key you created in the first step. |
Use the search token in your Shopify Liquid theme
To use the search token in your Shopify Liquid theme, call the app proxy route you created in the previous step. This section shows you how to do this in the context of Coveo Atomic.
Fetch the search token
In the snippets you use to initialize your pages, fetch the search token from the app proxy route and pass it to the snippet you’ll use to initialize the Coveo Headless engine.
<!-- snippets/initialize-search-page.liquid -->
<script type="module">
const [response] = await Promise.all([
fetch('/apps/token'),
window.loadAtomic(),
])
const token = (await response.json()).token;
const engine = await window.configureHeadless(token);
const searchPage = document.querySelector('#search-page');
searchPage.initializeWithEngine(window.CoveoEngine).then(function () {
searchPage.executeFirstRequest();
});
</script>
The configureHeadless function is defined in the snippet that initializes the Coveo Headless engine. |
Use the search token to configure the Headless engine
In the snippet that initializes the Coveo Headless engine, use the search token you fetched from the app proxy route to configure the engine.
<!-- snippets/engine.liquid -->
<script type="module">
window.configureHeadless = async (token) => {
const {buildShopifyCommerceEngine} =
await import('https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js');
if (window.CoveoEngine) {
return window.CoveoEngine;
}
window.CoveoEngine = buildShopifyCommerceEngine({
commerceEngineOptions: {
configuration: {
accessToken: token,
// ...
},
},
});
return window.CoveoEngine;
};
</script>
The search token you fetched from the app proxy route. |