Skip to content

๐Ÿ” 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 โ€‹

  1. Generate timestamp (Unix timestamp)
  2. Create string to sign from method + path + body + timestamp
  3. Calculate HMAC signature using SHA-256 and secret key
  4. Add headers to request (x-partner-id, x-timestamp, x-signature)
  5. 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:

  1. Check secret key is correct
  2. Verify string-to-sign format
  3. Ensure consistent JSON formatting
  4. Check for extra whitespace in body

โŒ Issue: "Request timestamp too old"

Error: AUTHENTICATION_FAILED - Request timestamp is too old

๐Ÿ”ง Solutions:

  1. Synchronize system clock
  2. Generate timestamp just before request
  3. Check timezone settings
  4. 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 โ€‹

  1. Postman Setup - Test your HMAC implementation with ready-to-use Postman collection
  2. Main System Flow - Complete workflow implementation
  3. API Examples - More implementation examples
  4. Error Handling - Robust error management

Your authentication implementation is now secure and production-ready! Test it with our Postman collection ๐Ÿ”

Released under the MIT License.