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-TimestampUnix timestamp of the request timeX-SignatureHMAC 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:
Example string to sign:
POST|https:www.mydomain.tld/myapp|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
- Extract headers
X-TimestampandX-Signature - Verify timestamp validity (±5 minutes tolerance recommended to avoid rejections due to clock drift)
- Rebuild the string to sign with the same parameters
- Calculate the expected HMAC with your secret
- 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_equalsin 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