Link Search Menu Expand Document

Authenticating Shopify Users

Contents

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_id is the Shopify customer ID (the same value sent in X-Spaaza-Session-Authentication-Point-Identifier)
  • internal_key is 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_id is the Shopify customer ID (the same value sent in X-Spaaza-Session-Authentication-Point-Identifier)
  • store_hash_external is 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:

  1. Prefer X-Spaaza-Session-User-Hash if the customer has the user_hash customer metafield available, as this uses a key that is only known to Spaaza.
  2. Fall back to X-Spaaza-Session-User-Hash-External if the user_hash customer metafield is not present. This header can always be computed at runtime from the store_hash_external shop 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-Identifier with the Shopify customer ID
  • If user_hash is available, send X-Spaaza-Session-User-Hash with that value
  • If user_hash is not available, send X-Spaaza-Session-User-Hash-External with 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.