# HMAC Authentication for Quable App

By
The Quable Team

# Overview

All requests sent from Quable PIM to your application can be authenticated using an HMAC (Hash-based Message Authentication Code) mechanism with SHA-256.

# How It Works

Authentication relies on two custom HTTP headers:

  • X-Timestamp Unix timestamp of the request time
  • X-Signature HMAC signature encoded in base64

# Generating the HMAC Signature

# 1. Building the String to Sign

The signature is calculated from a string constructed with the following elements, separated by the pipe character |:

{METHOD}|{ENDPOINT}|{TIMESTAMP}|{PAYLOAD}

Component Details:

Component Description Example
METHOD HTTP method in UPPERCASE POST, GET
ENDPOINT Endpoint path being called /api/v1/install
TIMESTAMP Unix timestamp (seconds since epoch) 1727712000
PAYLOAD Request body in JSON (empty for GET) {"key":"value"} or ""

Example string to sign:

POST|/api/v1|1727712000|{"object":{"type":"product","ids":["PROD1"]},"slot":"document.page.tab"}

# 2. Computing the HMAC-SHA256

The signature is calculated using:

  • Algorithm: HMAC-SHA256
  • Secret key: The shared secret provided by Quable during app configuration
  • Message: The string built in step 1
  • Format: Binary output (raw output)

# 3. Base64 Encoding

The binary HMAC result is then encoded in base64 to be transmitted in the HTTP header.

# Validating Requests in Your Application

Your application must validate each incoming request:

# Validation Steps

  1. Extract headers X-Timestamp and X-Signature
  2. Verify timestamp validity (±5 minutes tolerance recommended to avoid rejections due to clock drift)
  3. Rebuild the string to sign with the same parameters
  4. Calculate the expected HMAC with your secret
  5. Compare signatures securely (use constant-time comparison to prevent timing attacks)

# Validation Example (PHP)

public function validateRequest($method, $endpoint, $payload, $receivedTimestamp, $receivedSignature) {
    // Check timestamp (5 minutes tolerance)
    $currentTime = time();
    $timeDiff = abs($currentTime - (int)$receivedTimestamp);
    if ($timeDiff > 300) {
        throw new Exception('Request timestamp expired');
    }
    
    // Rebuild the string to sign
    $string_to_sign = implode('|', array(
        strtoupper($method),
        $endpoint,
        $receivedTimestamp,
        $payload
    ));
    
    // Calculate expected HMAC
    $expectedSignature = base64_encode(
        hash_hmac('sha256', $string_to_sign, $this->getSecret(), true)
    );
    
    // Secure signature comparison
    if (!hash_equals($expectedSignature, $receivedSignature)) {
        throw new Exception('Invalid signature');
    }
    
    return true;
}

# Best Practices

Do:

  • Store the secret securely (environment variables, vault)
  • Use constant-time comparison (hash_equals in PHP)
  • Implement a timestamp tolerance window (5 minutes recommended)
  • Log failed authentication attempts
  • Reject requests without authentication headers

Don't:

  • Hardcode the secret in your code
  • Compare signatures with == (vulnerable to timing attacks)
  • Accept timestamps that are too old or in the future
  • Ignore validation errors

# Troubleshooting

# Error: "Invalid signature"

Possible causes:

  • Incorrect or misconfigured secret
  • Wrong order of components in the string to sign
  • Different character encoding (watch out for UTF-8)
  • Modified or malformed payload
  • HTTP method in wrong case

Solution: Log the string to sign on both sides for comparison

# Error: "Request timestamp expired"

Possible causes:

  • Clocks out of sync between servers
  • Replayed request (replay attack)
  • High network latency

Solution: Check NTP synchronization, adjust tolerance window

# Resources