Authenticating Shopify Users
Contents
- Overview
- How Shopify user authentication replaces standard user authentication
- Required headers
- How the shop metafield is set up
- Choosing which hash header to use
- Constructing the headers in a Shopify storefront
Overview
When integrating a Shopify store with Spaaza, requests to the Spaaza API need to identify and authenticate the logged-in Shopify customer. This is done using a set of HTTP headers that are specific to the Shopify integration.
This page is intended for developers working on the frontend of a Shopify store who need to make authenticated requests to the Spaaza API on behalf of a logged-in Shopify customer.
How Shopify user authentication replaces standard user authentication
The standard Spaaza User Authentication method requires the X-Spaaza-Session-User-Id and X-Spaaza-Session-Key headers, which are obtained via the login endpoint.
For Shopify integrations, this login-based flow is replaced by a hash-based authentication mechanism. Instead of X-Spaaza-Session-User-Id and X-Spaaza-Session-Key, the following Shopify-specific headers are used:
| Standard header | Shopify replacement header |
|---|---|
X-Spaaza-Session-User-Id | X-Spaaza-Session-Authentication-Point-Identifier |
X-Spaaza-Session-Key | X-Spaaza-Session-User-Hash or X-Spaaza-Session-User-Hash-External (see below) |
This means that the Shopify storefront does not need to call the Spaaza login endpoint. Instead, it can construct the required headers directly from information available in the Shopify store.
Required headers
X-Spaaza-Session-Authentication-Point-Identifier
This header identifies the Shopify customer making the request. It must be populated with the Shopify customer ID of the logged-in customer.
The Shopify customer ID is a numeric identifier assigned by Shopify to each customer account. In Shopify Liquid templates, this is available as customer.id. In a headless storefront using the Shopify Storefront API, the customer ID can be obtained from the Customer object.
This header is always required when authenticating a Shopify user.
X-Spaaza-Session-User-Hash
This header contains a pre-generated HMAC-SHA256 hash that verifies the identity of the customer. The hash is computed as:
HMAC-SHA256(shopify_customer_id, internal_key)
Where:
shopify_customer_idis the Shopify customer ID (the same value sent inX-Spaaza-Session-Authentication-Point-Identifier)internal_keyis a secret key known only to Spaaza
Because the internal key is not available outside of Spaaza, this hash cannot be computed by the storefront at runtime. Instead, Spaaza pre-generates this hash value and stores it on the Shopify customer record as a customer metafield:
- Namespace:
customer - Key:
user_hash
In a Shopify Liquid template, this value can be accessed as:
{{ customer.metafields['customer'].user_hash }}
In a headless storefront, this metafield can be retrieved via the Shopify Storefront API by querying the Customer object’s metafield field with the appropriate namespace and key.
If this metafield is present on the customer, its value should be used as the X-Spaaza-Session-User-Hash header.
Note: Not all Shopify customers will have this metafield available. It is only present for customers whose records have been synchronised with Spaaza. See Choosing which hash header to use for how to handle this.
X-Spaaza-Session-User-Hash-External
This header contains an HMAC-SHA256 hash that can be computed by the storefront at runtime. The hash is computed as:
HMAC-SHA256(shopify_customer_id, store_hash_external)
Where:
shopify_customer_idis the Shopify customer ID (the same value sent inX-Spaaza-Session-Authentication-Point-Identifier)store_hash_externalis a key that is stored on the Shopify shop as a shop metafield
The store_hash_external shop metafield is accessible to the storefront and can be used to derive the hash at runtime without depending on a pre-generated per-customer value.
In a Shopify Liquid template, the hash can be computed server-side as follows:
{% assign store_hash = shop.metafields['shop'].store_hash_external %}
{% assign user_hash_external = customer.id | hmac_sha256: store_hash %}
In a headless storefront, the store_hash_external value can be retrieved via the Shopify Storefront API, and the HMAC-SHA256 hash can be computed in JavaScript (see Using a headless storefront).
The resulting user_hash_external value should be used as the X-Spaaza-Session-User-Hash-External header.
How the shop metafield is set up
When the Spaaza Shopify app is installed on a Shopify store, Spaaza automatically creates several shop metafields under the shop namespace. These include:
| Shop metafield key | Description |
|---|---|
store_hash_external | The key used by the storefront to compute the X-Spaaza-Session-User-Hash-External header via HMAC-SHA256 |
myprice_app_hostname | The hostname of the Spaaza app associated with the store |
chain_id | The Spaaza chain ID associated with the store |
These metafields are set up automatically during the Spaaza app installation process. No manual configuration is required by the store developer. The store_hash_external metafield is the key needed by the storefront to derive the external user hash at runtime.
In a Shopify Liquid template, these shop metafields can be accessed as:
{% assign store_hash = shop.metafields['shop'].store_hash_external %}
{% assign myprice_app_hostname = shop.metafields['shop'].myprice_app_hostname %}
{% assign chain_id = shop.metafields['shop'].chain_id %}
In a headless storefront, these values can be retrieved via the Shopify Storefront API (see Using a headless storefront).
Choosing which hash header to use
The Spaaza API accepts either X-Spaaza-Session-User-Hash or X-Spaaza-Session-User-Hash-External to authenticate a Shopify customer. The recommended approach is:
- Prefer
X-Spaaza-Session-User-Hashif the customer has theuser_hashcustomer metafield available, as this uses a key that is only known to Spaaza. - Fall back to
X-Spaaza-Session-User-Hash-Externalif theuser_hashcustomer metafield is not present. This header can always be computed at runtime from thestore_hash_externalshop metafield.
In practice, not every Shopify customer record will have the pre-generated user_hash customer metafield. This can happen when a customer record has not yet been fully synchronised with Spaaza. For this reason, the external hash is the more reliable option and should be used as a fallback.
A recommended pattern is to check for the presence of the user_hash customer metafield and, if it is not available, compute the external hash instead:
{% assign store_hash = shop.metafields['shop'].store_hash_external %}
{% if customer %}
{% assign customer_id = customer.id %}
{% assign user_hash = customer.metafields['customer'].user_hash %}
{% assign user_hash_external = customer_id | hmac_sha256: store_hash %}
{% endif %}
When making API requests, send the headers as follows:
- Always send
X-Spaaza-Session-Authentication-Point-Identifierwith the Shopify customer ID - If
user_hashis available, sendX-Spaaza-Session-User-Hashwith that value - If
user_hashis not available, sendX-Spaaza-Session-User-Hash-Externalwith the computed external hash value - It is also acceptable to send both hash headers; the API will use whichever is available
Constructing the headers in a Shopify storefront
Using Liquid templates
For traditional Shopify themes that use Liquid, the authentication values can be assembled server-side in a Liquid template and then passed to JavaScript for use in API requests.
Assemble the values in your Liquid template:
{% assign store_hash = shop.metafields['shop'].store_hash_external %}
{% assign myprice_app_hostname = shop.metafields['shop'].myprice_app_hostname %}
{% assign chain_id = shop.metafields['shop'].chain_id %}
{% if customer %}
{% assign customer_id = customer.id %}
{% assign user_hash = customer.metafields['customer'].user_hash %}
{% assign user_hash_external = customer_id | hmac_sha256: store_hash %}
{% endif %}
These values can then be passed to your JavaScript code (for example via data attributes on an HTML element) to construct the HTTP headers for Spaaza API requests:
// Example: reading values from data attributes and constructing headers
const headers = {
'X-Spaaza-Session-Authentication-Point-Identifier': customerId,
'X-Spaaza-MyPrice-App-Hostname': mypriceAppHostname
};
// Prefer the pre-generated user_hash if available, otherwise use the external hash
if (userHash) {
headers['X-Spaaza-Session-User-Hash'] = userHash;
} else {
headers['X-Spaaza-Session-User-Hash-External'] = userHashExternal;
}
Using a headless storefront (Hydrogen/React)
For headless Shopify storefronts built with Hydrogen, React, or other JavaScript frameworks, metafield values are retrieved via the Shopify Storefront API instead of Liquid templates, and the HMAC-SHA256 hash is computed in JavaScript.
Retrieving metafields via the Storefront API
Use GraphQL queries to fetch the shop and customer metafields. The shop metafields can be retrieved using the shop query:
query {
shop {
metafield(namespace: "shop", key: "store_hash_external") {
value
}
}
}
The customer metafields (including the pre-generated user_hash) can be retrieved when querying the logged-in customer:
query {
customer(customerAccessToken: "...") {
id
metafield(namespace: "customer", key: "user_hash") {
value
}
}
}
Computing HMAC-SHA256 in JavaScript
In a Liquid template, the hmac_sha256 filter computes the hash server-side. In a headless storefront, this must be done in JavaScript. The following example uses the Web Crypto API, which is available in modern browsers and in server-side JavaScript runtimes:
async function computeHmacSha256(message, key) {
const encoder = new TextEncoder();
const keyData = encoder.encode(key);
const msgData = encoder.encode(String(message));
const cryptoKey = await crypto.subtle.importKey(
'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, msgData);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
Assembling the headers
Once the metafield values have been retrieved and the hash computed, the headers can be constructed in the same way as in the Liquid approach:
// Retrieve metafield values from Storefront API responses
const storeHashExternal = shopData.shop.metafield.value;
const customerId = customerData.customer.id;
const userHash = customerData.customer.metafield?.value; // may be null
// Compute the external hash
const userHashExternal = await computeHmacSha256(customerId, storeHashExternal);
// Build the headers
const headers = {
'X-Spaaza-Session-Authentication-Point-Identifier': customerId,
'X-Spaaza-MyPrice-App-Hostname': mypriceAppHostname
};
if (userHash) {
headers['X-Spaaza-Session-User-Hash'] = userHash;
} else {
headers['X-Spaaza-Session-User-Hash-External'] = userHashExternal;
}
For more information on how to embed Spaaza UI components in your Shopify storefront using these values, see Customising the Shopify UX.