Skip to content

HMAC Authentication Overview

HMAC Authentication v1 ensures secure communication between external systems using cryptographic signatures to verify request integrity and authenticity.

What is HMAC Authentication?

HMAC (Hash-based Message Authentication Code) is a mechanism for verifying both the data integrity and authenticity of a message using a shared secret key.

In this integration:

  • Client System (OLT Management) signs every request with HMAC-SHA256
  • Server System (External API Server) verifies the signature before processing
  • Both parties share a secret key that never travels over the network

Why HMAC Authentication?

Security Benefits

FeatureDescription
IntegrityDetects any tampering with request data
AuthenticityProves the request comes from authorized source
Replay ProtectionTimestamp window + unique request ID prevents replay
No Password TransmissionSecret key never sent over the network

Use Cases

Service-to-Service Communication - Backend systems authenticating with each other

API Gateway Integration - Secure routing between internal services

Multi-Tenant Systems - Credential isolation per tenant/organization

How It Works

┌────────────┐                                   ┌────────────────┐
│ OLT Mgmt   │                                   │ External API   │
│  (Client)  │                                   │    (Server)    │
└─────┬──────┘                                   └──────┬─────────┘
      │                                                 │
      │ 1. Prepare request                              │
      │    (method, path, headers, body)                │
      │                                                 │
      │ 2. Generate canonical request                   │
      │    (standardized format)                        │
      │                                                 │
      │ 3. Create string to sign                        │
      │    (HMAC-SHA256 + timestamp + hash)             │
      │                                                 │
      │ 4. Sign with secret key                         │
      │    (HMAC-SHA256 signature)                      │
      │                                                 │
      │ 5. Send request with signature                  │
      ├────────────────────────────────────────────────>│
      │                                                 │
      │                            6. Check timestamp   │
      │                               (within 5 min)    │
      │                                                 │
      │                            7. Verify signature  │
      │                               (recalculate &    │
      │                                compare)         │
      │                                                 │
      │ 8. Response (success / error)                   │
      │<────────────────────────────────────────────────┤
      │                                                 │

Authentication Flow

Request Signing (Client Side)

  1. Build Request - Define HTTP method, path, headers, and body
  2. Create Canonical Request - Normalize request into standard format
  3. Generate String to Sign - Combine algorithm, timestamp, and request hash
  4. Calculate Signature - HMAC-SHA256 with secret key
  5. Add Authorization Header - Include credential type, ID, and signature

Signature Verification (Server Side)

  1. Extract Headers - Parse Authorization, X-Date, and other required headers
  2. Validate Timestamp - Ensure request is within acceptable time window (fail fast)
  3. Reconstruct Canonical Request - Build same format as client
  4. Recalculate Signature - Use same algorithm with stored secret key
  5. Compare Signatures - Constant-time comparison to prevent timing attacks

Key Components

Required Headers

Service Authentication:

http
Authorization: HMAC-SHA256 CredentialType=service, CredentialId=external-system-client, SignedHeaders=x-date;x-request-id;x-client-type, Signature=3c92776b...
X-Date: 2025-10-14T03:52:12Z
X-Request-Id: bf6c5652-9656-4167-b1d4-10c690a72102
X-Client-Type: service

User Authentication:

http
Authorization: HMAC-SHA256 CredentialType=user, CredentialId=admin@telkom.com, SignedHeaders=x-date;x-tenant-id;x-request-id;x-client-type, Signature=5d83ac7f...
X-Date: 2025-10-14T03:52:12Z
X-Request-Id: af7d4523-8765-4278-a2e5-21d801b83456
X-Client-Type: web
X-Tenant-Id: isp-telkom

Credential Types

TypeFormatUsage
ServiceCredentialType=service, CredentialId=external-system-clientSystem-to-system authentication
UserCredentialType=user, CredentialId=john@example.comUser-initiated requests (requires X-Tenant-Id)

Time Window

  • Default: 5 minutes tolerance
  • Prevents replay attacks
  • Server compares request timestamp with current time

Implementation Example

Canonical Request Format

Service Authentication:

POST
/api/v1/users
filter=active&sort=name
x-client-type:service
x-date:2025-10-14T03:52:12Z
x-request-id:bf6c5652-9656-4167-b1d4-10c690a72102

x-date;x-request-id;x-client-type
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

User Authentication:

GET
/api/v1/onu/unconfigured

x-client-type:web
x-date:2025-10-14T03:52:12Z
x-request-id:af7d4523-8765-4278-a2e5-21d801b83456
x-tenant-id:isp-telkom

x-date;x-tenant-id;x-request-id;x-client-type
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Signature Calculation (TypeScript)

typescript
import crypto from 'crypto';

interface SignatureParams {
  method: string;
  url: string;
  headers: Record<string, string>;
  body: string;
  secretKey: string;
  isUserAuth?: boolean;
}

function calculateSignature(params: SignatureParams): string {
  const { method, url, headers, body, secretKey, isUserAuth = false } = params;
  
  const u = new URL(url);
  const path = u.pathname;
  const queryString = u.search.slice(1);
  
  // Hash body
  const bodyHash = crypto
    .createHash('sha256')
    .update(body || '')
    .digest('hex');
  
  // Determine signed headers based on auth type
  const signedHeaderNames = isUserAuth
    ? ['x-client-type', 'x-date', 'x-request-id', 'x-tenant-id']
    : ['x-client-type', 'x-date', 'x-request-id'];
  
  // Build canonical headers (lowercase, sorted, with trailing newline)
  const canonicalHeaders = signedHeaderNames
    .map(name => `${name}:${headers[name]}`)
    .join('\n') + '\n';
  
  const signedHeaders = signedHeaderNames.join(';');
  
  // Build canonical request
  const canonicalRequest = [
    method.toUpperCase(),
    path,
    queryString,
    canonicalHeaders,
    signedHeaders,
    bodyHash
  ].join('\n');
  
  // Create string to sign
  const stringToSign = [
    'HMAC-SHA256',
    headers['x-date'],
    crypto.createHash('sha256').update(canonicalRequest).digest('hex')
  ].join('\n');
  
  // Calculate signature
  return crypto
    .createHmac('sha256', secretKey)
    .update(stringToSign)
    .digest('hex');
}

// Example Usage - Service Authentication
const serviceSignature = calculateSignature({
  method: 'POST',
  url: 'https://olt.remala.local/api/v1/users?filter=active&sort=name',
  headers: {
    'x-date': '2025-10-14T03:52:12Z',
    'x-request-id': 'bf6c5652-9656-4167-b1d4-10c690a72102',
    'x-client-type': 'service'
  },
  body: '',
  secretKey: 'your-service-secret-key'
});

// Example Usage - User Authentication
const userSignature = calculateSignature({
  method: 'GET',
  url: 'https://olt.remala.local/api/v1/onu/unconfigured',
  headers: {
    'x-date': '2025-10-14T03:52:12Z',
    'x-request-id': 'af7d4523-8765-4278-a2e5-21d801b83456',
    'x-client-type': 'web',
    'x-tenant-id': 'isp-telkom'
  },
  body: '',
  secretKey: 'tenant-secret-key',
  isUserAuth: true
});

Error Handling

Common Error Responses

typescript
// 401 Unauthorized - Missing or invalid Authorization header
{
  "error": "unauthorized",
  "message": "Missing Authorization header"
}

// 401 Unauthorized - Invalid format
{
  "error": "unauthorized",
  "message": "Invalid Authorization format"
}

// 401 Unauthorized - Signature mismatch
{
  "error": "unauthorized",
  "message": "Invalid signature"
}

// 401 Unauthorized - Expired timestamp
{
  "error": "unauthorized",
  "message": "Signature expired"
}

// 401 Unauthorized - Missing required headers
{
  "error": "unauthorized",
  "message": "Missing required headers"
}

// 400 Bad Request - Malformed header
{
  "error": "bad_request",
  "message": "Invalid X-Date format. Expected ISO 8601 (UTC)"
}

Troubleshooting

Signature Mismatch

Check:

  1. Secret key matches on both client and server
  2. Canonical request format is identical
  3. Header order follows server requirement exactly
  4. Header names are lowercase in canonical request
  5. Canonical headers end with newline (\n)
  6. Query string included as separate component
  7. Body hash is calculated correctly (use empty string for GET/DELETE)
  8. Using correct Authorization format (CredentialType and CredentialId, not Credential=type:id)

Timestamp Expired

Solutions:

  1. Sync system clocks (use NTP)
  2. Reduce network latency
  3. Generate timestamp immediately before request
  4. Verify timezone is UTC

Invalid Authorization Format

Common Issue:

http
❌ Wrong (old format):
Authorization: HMAC-SHA256 Credential=service:external-system-client, ...

✅ Correct (new format):
Authorization: HMAC-SHA256 CredentialType=service, CredentialId=external-system-client, ...

Common Mistakes

❌ Using uppercase in header names (in canonical request) ❌ Wrong SignedHeaders order ❌ Missing trailing newline in canonical headers ❌ Excluding query string from canonical request ❌ Including Authorization header in SignedHeaders ❌ Wrong body hash algorithm (must be SHA256) ❌ Timezone mismatch in timestamp (must be UTC) ❌ Using old Authorization format (Credential=type:id)

Authentication Type Comparison

Service Authentication vs User Authentication

FeatureService AuthenticationUser Authentication
Credential FormatCredentialType=service, CredentialId=slugCredentialType=user, CredentialId=email
X-Tenant-Id HeaderNot requiredRequired
Signed Headersx-date;x-request-id;x-client-typex-date;x-tenant-id;x-request-id;x-client-type
Use CaseService-to-service (register ISP)Tenant operations (ONU management)
Secret Key SourceIntegration secretPartner/tenant secret

Quick Comparison

HMAC vs Other Auth Methods

MethodProsConsUse Case
HMACNo password in transit, replay protectionRequires time sync, complex implementationService-to-service
JWTStateless, includes claimsToken can be stolen if not securedUser sessions
API KeySimpleKey in every request, no integrity checkPublic APIs
OAuth 2.0Industry standard, token refreshComplex setup, requires auth serverThird-party access

Next Steps