๐ 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);
}
}
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 make_request(self, method: str, path: str, body: Optional[Dict] = None) -> Dict[str, Any]:
"""Make authenticated API request"""
timestamp = int(time.time())
signature = self.generate_signature(method, path, body, timestamp)
headers = {
'Content-Type': 'application/json',
'x-partner-id': self.partner_id,
'x-timestamp': str(timestamp),
'x-signature': signature
}
url = f"{self.base_url}{path}"
try:
response = requests.request(
method=method.lower(),
url=url,
headers=headers,
json=body,
timeout=30
)
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)}")
๐ 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");
โ Insecure Practices:
javascript
// Never hardcode in source code
const secretKey = "sk_live_1234567890abcdef"; // โ DON'T DO THIS
2. Timestamp Validation โ
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. HTTPS Only โ
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() },
];
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");
}
}
}
๐ ๏ธ 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
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("Generated Signature:", signature);
console.log("Partner ID:", partnerId);
return {
"x-partner-id": partnerId,
"x-timestamp": timestamp,
"x-signature": signature,
};
}
๐ฏ Next Steps โ
- Postman Setup - Test your HMAC implementation with ready-to-use Postman collection
- Main System Flow - Complete workflow implementation
- API Examples - More implementation examples
- Error Handling - Robust error management
Your authentication implementation is now secure and production-ready! Test it with our Postman collection ๐