🔐 HMAC Authentication Implementation Guide
📋 Overview
Partner Services uses HMAC SHA-256 authentication for secure API access. This guide provides complete implementation details for multiple programming languages with working code examples.
Security Features:
- HMAC SHA-256 signature verification
- Timestamp validation (5-minute window)
- Request body integrity checking
- Partner-specific secret keys
- Replay attack prevention
🔧 Authentication Flow
Step-by-Step Process
- Generate timestamp (Unix timestamp)
- Create string to sign from method + path + body + timestamp
- Calculate HMAC signature using SHA-256 and secret key
- Add headers to request (
x-partner-id
,x-timestamp
,x-signature
) - Send request to API endpoint
String to Sign Format
{HTTP_METHOD}\n{REQUEST_PATH}\n{REQUEST_BODY}\n{TIMESTAMP}
Example:
POST\n/api/v1/shipments/calculate-charges\n{"weight":2500,"pickupPincode":"110001"}\n1642752000
💻 Implementation Examples
Node.js / JavaScript
Complete Implementation
javascript
const crypto = require("crypto");
const axios = require("axios");
class PartnerServicesAuth {
constructor(config) {
this.baseURL = config.baseURL;
this.partnerId = config.partnerId;
this.secretKey = config.secretKey;
}
/**
* Generate HMAC SHA-256 signature
* @param {string} method - HTTP method (GET, POST, etc.)
* @param {string} path - Request path (/api/v1/...)
* @param {object|null} body - Request body object
* @param {number} timestamp - Unix timestamp
* @returns {string} HMAC signature
*/
generateSignature(method, path, body, timestamp) {
// Convert body to JSON string or empty string
const bodyStr = body ? JSON.stringify(body) : "";
// Create string to sign
const stringToSign = `${method}\n${path}\n${bodyStr}\n${timestamp}`;
// Generate HMAC SHA-256 signature
return crypto
.createHmac("sha256", this.secretKey)
.update(stringToSign)
.digest("hex");
}
/**
* Generate authentication headers
* @param {string} method - HTTP method
* @param {string} path - Request path
* @param {object|null} body - Request body
* @returns {object} Headers object
*/
generateAuthHeaders(method, path, body = null) {
const timestamp = Math.floor(Date.now() / 1000);
const signature = this.generateSignature(method, path, body, timestamp);
return {
"Content-Type": "application/json",
"x-partner-id": this.partnerId,
"x-timestamp": timestamp,
"x-signature": signature,
};
}
/**
* Make authenticated API request
* @param {string} method - HTTP method
* @param {string} path - Request path
* @param {object|null} body - Request body
* @returns {Promise} API response
*/
async makeRequest(method, path, body = null) {
const headers = this.generateAuthHeaders(method, path, body);
try {
const response = await axios({
method: method.toLowerCase(),
url: `${this.baseURL}${path}`,
headers,
data: body,
});
return response.data;
} catch (error) {
// Enhanced error handling
if (error.response) {
const errorData = error.response.data;
console.error("API Error:", {
status: error.response.status,
code: errorData?.error?.code,
message: errorData?.error?.message,
details: errorData?.error?.details,
});
throw new Error(
`API Error: ${errorData?.error?.message || "Unknown error"}`
);
} else {
console.error("Network Error:", error.message);
throw new Error(`Network Error: ${error.message}`);
}
}
}
}
// Usage Example
const auth = new PartnerServicesAuth({
baseURL: "https://partner-services.logistics.com/api/v1",
partnerId: "your_partner_id",
secretKey: "your_secret_key",
});
// Test authentication
async function testAuth() {
try {
const result = await auth.makeRequest("GET", "/health");
console.log("✅ Authentication successful:", result);
} catch (error) {
console.error("❌ Authentication failed:", error.message);
}
}
// Calculate charges example
async function calculateCharges() {
const shipmentData = {
pickupPincode: "110001",
deliveryPincode: "400001",
weight: 2500,
shipmentType: "B2C",
paymentType: "COD",
};
try {
const result = await auth.makeRequest(
"POST",
"/shipments/calculate-charges",
shipmentData
);
console.log("✅ Charges calculated:", result.data.charges);
} catch (error) {
console.error("❌ Calculation failed:", error.message);
}
}
Browser/Frontend Implementation
javascript
class BrowserPartnerAuth {
constructor(config) {
this.baseURL = config.baseURL;
this.partnerId = config.partnerId;
this.secretKey = config.secretKey; // ⚠️ Consider server-side proxy for security
}
async generateSignature(method, path, body, timestamp) {
const bodyStr = body ? JSON.stringify(body) : "";
const stringToSign = `${method}\n${path}\n${bodyStr}\n${timestamp}`;
// Use Web Crypto API for HMAC
const encoder = new TextEncoder();
const keyData = encoder.encode(this.secretKey);
const messageData = encoder.encode(stringToSign);
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
return Array.from(new Uint8Array(signature))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
async makeRequest(method, path, body = null) {
const timestamp = Math.floor(Date.now() / 1000);
const signature = await this.generateSignature(
method,
path,
body,
timestamp
);
const headers = {
"Content-Type": "application/json",
"x-partner-id": this.partnerId,
"x-timestamp": timestamp,
"x-signature": signature,
};
const response = await fetch(`${this.baseURL}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : null,
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || "API request failed");
}
return await response.json();
}
}
Python Implementation
python
import hashlib
import hmac
import json
import time
import requests
from typing import Optional, Dict, Any
class PartnerServicesAuth:
def __init__(self, base_url: str, partner_id: str, secret_key: str):
self.base_url = base_url
self.partner_id = partner_id
self.secret_key = secret_key.encode('utf-8')
def generate_signature(self, method: str, path: str, body: Optional[Dict], timestamp: int) -> str:
"""Generate HMAC SHA-256 signature"""
# Convert body to JSON string or empty string
body_str = json.dumps(body, separators=(',', ':')) if body else ''
# Create string to sign
string_to_sign = f"{method}\n{path}\n{body_str}\n{timestamp}"
# Generate HMAC SHA-256 signature
signature = hmac.new(
self.secret_key,
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
def generate_auth_headers(self, method: str, path: str, body: Optional[Dict] = None) -> Dict[str, str]:
"""Generate authentication headers"""
timestamp = int(time.time())
signature = self.generate_signature(method, path, body, timestamp)
return {
'Content-Type': 'application/json',
'x-partner-id': self.partner_id,
'x-timestamp': str(timestamp),
'x-signature': signature
}
def make_request(self, method: str, path: str, body: Optional[Dict] = None) -> Dict[str, Any]:
"""Make authenticated API request"""
headers = self.generate_auth_headers(method, path, body)
url = f"{self.base_url}{path}"
try:
response = requests.request(
method=method.lower(),
url=url,
headers=headers,
json=body,
timeout=30
)
# Raise exception for HTTP errors
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
error_data = response.json() if response.content else {}
error_message = error_data.get('error', {}).get('message', 'Unknown error')
raise Exception(f"API Error: {error_message}")
except requests.exceptions.RequestException as e:
raise Exception(f"Network Error: {str(e)}")
# Usage Example
auth = PartnerServicesAuth(
base_url='https://partner-services.logistics.com/api/v1',
partner_id='your_partner_id',
secret_key='your_secret_key'
)
# Test authentication
try:
result = auth.make_request('GET', '/health')
print('✅ Authentication successful:', result)
except Exception as e:
print('❌ Authentication failed:', str(e))
# Calculate charges
shipment_data = {
'pickupPincode': '110001',
'deliveryPincode': '400001',
'weight': 2500,
'shipmentType': 'B2C',
'paymentType': 'COD'
}
try:
result = auth.make_request('POST', '/shipments/calculate-charges', shipment_data)
print('✅ Charges calculated:', result['data']['charges'])
except Exception as e:
print('❌ Calculation failed:', str(e))
PHP Implementation
php
<?php
class PartnerServicesAuth {
private $baseUrl;
private $partnerId;
private $secretKey;
public function __construct($baseUrl, $partnerId, $secretKey) {
$this->baseUrl = $baseUrl;
$this->partnerId = $partnerId;
$this->secretKey = $secretKey;
}
/**
* Generate HMAC SHA-256 signature
*/
public function generateSignature($method, $path, $body, $timestamp) {
// Convert body to JSON string or empty string
$bodyStr = $body ? json_encode($body, JSON_UNESCAPED_SLASHES) : '';
// Create string to sign
$stringToSign = "{$method}\n{$path}\n{$bodyStr}\n{$timestamp}";
// Generate HMAC SHA-256 signature
return hash_hmac('sha256', $stringToSign, $this->secretKey);
}
/**
* Generate authentication headers
*/
public function generateAuthHeaders($method, $path, $body = null) {
$timestamp = time();
$signature = $this->generateSignature($method, $path, $body, $timestamp);
return [
'Content-Type' => 'application/json',
'x-partner-id' => $this->partnerId,
'x-timestamp' => (string)$timestamp,
'x-signature' => $signature
];
}
/**
* Make authenticated API request
*/
public function makeRequest($method, $path, $body = null) {
$headers = $this->generateAuthHeaders($method, $path, $body);
$url = $this->baseUrl . $path;
// Prepare cURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_HTTPHEADER => $this->formatHeaders($headers),
CURLOPT_POSTFIELDS => $body ? json_encode($body, JSON_UNESCAPED_SLASHES) : null
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("Network Error: $error");
}
$responseData = json_decode($response, true);
if ($httpCode >= 400) {
$errorMessage = $responseData['error']['message'] ?? 'Unknown error';
throw new Exception("API Error: $errorMessage");
}
return $responseData;
}
/**
* Format headers for cURL
*/
private function formatHeaders($headers) {
$formatted = [];
foreach ($headers as $key => $value) {
$formatted[] = "$key: $value";
}
return $formatted;
}
}
// Usage Example
$auth = new PartnerServicesAuth(
'https://partner-services.logistics.com/api/v1',
'your_partner_id',
'your_secret_key'
);
// Test authentication
try {
$result = $auth->makeRequest('GET', '/health');
echo "✅ Authentication successful: " . json_encode($result) . "\n";
} catch (Exception $e) {
echo "❌ Authentication failed: " . $e->getMessage() . "\n";
}
// Calculate charges
$shipmentData = [
'pickupPincode' => '110001',
'deliveryPincode' => '400001',
'weight' => 2500,
'shipmentType' => 'B2C',
'paymentType' => 'COD'
];
try {
$result = $auth->makeRequest('POST', '/shipments/calculate-charges', $shipmentData);
echo "✅ Charges calculated: " . json_encode($result['data']['charges']) . "\n";
} catch (Exception $e) {
echo "❌ Calculation failed: " . $e->getMessage() . "\n";
}
?>
🔒 Security Best Practices
1. Secret Key Management
✅ Secure Practices:
javascript
// Use environment variables
const secretKey = process.env.PARTNER_SERVICES_SECRET_KEY;
// Use secure key management services
const secretKey = await getSecretFromVault("partner-services-key");
// Rotate keys regularly
const secretKey = await getCurrentActiveKey();
❌ Insecure Practices:
javascript
// Never hardcode in source code
const secretKey = "sk_live_1234567890abcdef"; // ❌ DON'T DO THIS
// Never commit to version control
// Never log secret keys
// Never send keys in API responses
2. Timestamp Validation
Implementation:
javascript
function validateTimestamp(requestTimestamp) {
const currentTime = Math.floor(Date.now() / 1000);
const timeDiff = Math.abs(currentTime - requestTimestamp);
// Allow 5-minute window (300 seconds)
if (timeDiff > 300) {
throw new Error("Request timestamp is too old or too far in the future");
}
return true;
}
3. Request Body Integrity
Ensure consistency:
javascript
// Always use the same JSON formatting
const bodyStr = body ? JSON.stringify(body, null, 0) : "";
// Avoid extra whitespace or formatting differences
// Use consistent key ordering if possible
4. HTTPS Only
Always use HTTPS in production:
javascript
const baseURL = "https://partner-services.logistics.com/api/v1"; // ✅ Secure
// Never use HTTP in production: http://... // ❌ Insecure
🧪 Testing Authentication
Authentication Test Suite
javascript
class AuthTestSuite {
constructor(auth) {
this.auth = auth;
}
async runAllTests() {
const tests = [
{ name: "Basic Authentication", test: () => this.testBasicAuth() },
{ name: "Invalid Partner ID", test: () => this.testInvalidPartnerId() },
{ name: "Invalid Signature", test: () => this.testInvalidSignature() },
{ name: "Old Timestamp", test: () => this.testOldTimestamp() },
{ name: "Future Timestamp", test: () => this.testFutureTimestamp() },
{ name: "Body Integrity", test: () => this.testBodyIntegrity() },
];
const results = [];
for (const test of tests) {
try {
await test.test();
results.push({ name: test.name, status: "PASSED" });
console.log(`✅ ${test.name}: PASSED`);
} catch (error) {
results.push({
name: test.name,
status: "FAILED",
error: error.message,
});
console.log(`❌ ${test.name}: FAILED - ${error.message}`);
}
}
return results;
}
async testBasicAuth() {
const result = await this.auth.makeRequest("GET", "/health");
if (!result.success) {
throw new Error("Basic authentication failed");
}
}
async testInvalidPartnerId() {
const invalidAuth = { ...this.auth, partnerId: "invalid_partner" };
try {
await invalidAuth.makeRequest("GET", "/health");
throw new Error("Should have failed with invalid partner ID");
} catch (error) {
if (!error.message.includes("AUTHENTICATION_FAILED")) {
throw error;
}
}
}
async testInvalidSignature() {
const invalidAuth = { ...this.auth, secretKey: "invalid_secret" };
try {
await invalidAuth.makeRequest("GET", "/health");
throw new Error("Should have failed with invalid signature");
} catch (error) {
if (!error.message.includes("AUTHENTICATION_FAILED")) {
throw error;
}
}
}
async testOldTimestamp() {
// Test with timestamp 10 minutes ago (should fail)
const oldTimestamp = Math.floor(Date.now() / 1000) - 600;
const signature = this.auth.generateSignature(
"GET",
"/health",
null,
oldTimestamp
);
try {
await axios.get(`${this.auth.baseURL}/health`, {
headers: {
"x-partner-id": this.auth.partnerId,
"x-timestamp": oldTimestamp,
"x-signature": signature,
},
});
throw new Error("Should have failed with old timestamp");
} catch (error) {
if (!error.response?.data?.error?.code?.includes("TIMESTAMP")) {
throw error;
}
}
}
async testFutureTimestamp() {
// Test with timestamp 10 minutes in future (should fail)
const futureTimestamp = Math.floor(Date.now() / 1000) + 600;
const signature = this.auth.generateSignature(
"GET",
"/health",
null,
futureTimestamp
);
try {
await axios.get(`${this.auth.baseURL}/health`, {
headers: {
"x-partner-id": this.auth.partnerId,
"x-timestamp": futureTimestamp,
"x-signature": signature,
},
});
throw new Error("Should have failed with future timestamp");
} catch (error) {
if (!error.response?.data?.error?.code?.includes("TIMESTAMP")) {
throw error;
}
}
}
async testBodyIntegrity() {
const originalBody = { weight: 2500, pincode: "110001" };
const modifiedBody = { weight: 2500, pincode: "110002" }; // Modified after signing
// Sign with original body
const timestamp = Math.floor(Date.now() / 1000);
const signature = this.auth.generateSignature(
"POST",
"/test",
originalBody,
timestamp
);
try {
// Send with modified body (should fail)
await axios.post(
`${this.auth.baseURL}/shipments/calculate-charges`,
modifiedBody,
{
headers: {
"Content-Type": "application/json",
"x-partner-id": this.auth.partnerId,
"x-timestamp": timestamp,
"x-signature": signature,
},
}
);
throw new Error("Should have failed with modified body");
} catch (error) {
if (!error.response?.data?.error?.code?.includes("SIGNATURE")) {
throw error;
}
}
}
}
// Run tests
const auth = new PartnerServicesAuth(config);
const testSuite = new AuthTestSuite(auth);
testSuite.runAllTests().then((results) => {
console.log("\n📊 Test Results:");
const passed = results.filter((r) => r.status === "PASSED").length;
console.log(`${passed}/${results.length} tests passed`);
});
🛠️ Debugging Authentication Issues
Common Issues & Solutions
❌ Issue: "Invalid HMAC signature"
Error: AUTHENTICATION_FAILED - Invalid HMAC signature
🔧 Solutions:
- Check secret key is correct
- Verify string-to-sign format
- Ensure consistent JSON formatting
- Check for extra whitespace in body
❌ Issue: "Request timestamp too old"
Error: AUTHENTICATION_FAILED - Request timestamp is too old
🔧 Solutions:
- Synchronize system clock
- Generate timestamp just before request
- Check timezone settings
- Verify timestamp is in Unix format
❌ Issue: "Partner not found"
Error: PARTNER_NOT_FOUND - Partner ID not found
🔧 Solutions:
- Verify partner ID is correct
- Check partner account is active
- Ensure partner has API access enabled
Debug Helper Function
javascript
function debugAuthGeneration(method, path, body, partnerId, secretKey) {
const timestamp = Math.floor(Date.now() / 1000);
const bodyStr = body ? JSON.stringify(body) : "";
const stringToSign = `${method}\n${path}\n${bodyStr}\n${timestamp}`;
const signature = crypto
.createHmac("sha256", secretKey)
.update(stringToSign)
.digest("hex");
console.log("🔍 Authentication Debug Information:");
console.log("Method:", method);
console.log("Path:", path);
console.log("Body String:", bodyStr);
console.log("Timestamp:", timestamp);
console.log("String to Sign:", JSON.stringify(stringToSign));
console.log("Secret Key Length:", secretKey.length);
console.log("Generated Signature:", signature);
console.log("Partner ID:", partnerId);
return {
"x-partner-id": partnerId,
"x-timestamp": timestamp,
"x-signature": signature,
};
}
⚡ Performance Optimization
Caching Authentication Headers
javascript
class CachedPartnerAuth extends PartnerServicesAuth {
constructor(config) {
super(config);
this.signatureCache = new Map();
this.cacheTimeout = 60; // 1 minute cache
}
generateCachedSignature(method, path, body, timestamp) {
const cacheKey = `${method}:${path}:${JSON.stringify(body)}:${timestamp}`;
if (this.signatureCache.has(cacheKey)) {
return this.signatureCache.get(cacheKey);
}
const signature = this.generateSignature(method, path, body, timestamp);
// Cache with expiration
setTimeout(() => {
this.signatureCache.delete(cacheKey);
}, this.cacheTimeout * 1000);
this.signatureCache.set(cacheKey, signature);
return signature;
}
}
Connection Pooling
javascript
const axios = require("axios");
const https = require("https");
// Create HTTPS agent with connection pooling
const httpsAgent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
});
// Configure axios with connection pooling
const apiClient = axios.create({
httpsAgent,
timeout: 30000,
maxRedirects: 3,
});
🎯 Next Steps
- Main System Flow - Complete workflow implementation
- Performance Optimization - Caching and optimization strategies
- Error Handling - Robust error management
- API Examples - More implementation examples
Your authentication implementation is now secure and production-ready! 🔐