'Azure REST Api in PHP giving "Given value does not match HMAC header structure"

I am trying to connect to the Azure Communications API to send SMS messages using the REST endpoint.

The link to the API instructions is here: https://docs.microsoft.com/en-us/azure/azure-app-configuration/rest-api-authentication-hmac

My PHP code is as follows:

    public static function send(array $phoneNumbers, string $message)
    {
        $body = json_encode([
            'from' => config("azure.sms_phone_number"),
            'message' => $message,
            'smsRecipients' => array_map(fn ($num) => ['to' => $num], $phoneNumbers)
        ]);

        $endpoint = parse_url(config("azure.sms_endpoint"));

        $headers = [
            'Date' => gmdate("D, d M y H:i:s T"),
            'host' => $endpoint['host'],
            'x-ms-content-sha256' => base64_encode(hash('sha256', $body, true)),
            'Content-Type' => 'application/json',
            'Accept' => '*/*'
        ];

        $stringToSign = utf8_encode("POST\n" . $endpoint['path'] . "?" . $endpoint['query'] . "\n" . implode(";", array_values($headers)));

        $headers['Authorization'] = implode("&", [
            "HMAC-SHA256 Credential=" . config("azure.sms_key_id"),
            "SignedHeaders=" . implode(";", array_keys($headers)),
            'Signature=' . base64_encode(hash_hmac('sha256', $stringToSign, base64_decode(config("azure.sms_key")), true))
        ]);

        $client = new Client();

        $client->post(config("azure.sms_endpoint"), [
            'headers' => $headers,
            'body' => $body,
            'debug' => true
        ]);
    }

Below are some of the variables

$body = '{"from":"+1844295xxx","message":"hi","smsRecipients":[{"to":"2019160xxx"}]};';

$stringToSign = 'POST
/sms?api-version=2021-03-07
Mon, 14 Mar 22 17:09:17 GMT;testxxxxxx.communication.azure.com;UXoK8141pppkVedAkc+eSQBqKOWciyoiq+AG/xxxxxx=;application/json;*/*;';

$headers = 
(
    [Date] => Mon, 14 Mar 22 17:09:17 GMT
    [host] => testxxxxx.communication.azure.com
    [x-ms-content-sha256] => UXoK8141pppkVedAkc+eSQBqKOWciyoiq+AG/xxxxxx=
    [Content-Type] => application/json
    [Accept] => */*
    [Authorization] => HMAC-SHA256 Credential=primaryKey&SignedHeaders=Date;host;x-ms-content-sha256;Content-Type;Accept&Signature=R8M7+fODzXaxXHbdcV5CHXiEq5R/7Fvd9VGYxxxxxxx=
)

The result I get is this:

Client error: `POST https://test.communication.azure.com/sms?api-version=2021-03-07` resulted in a `401 Unauthorized` response:
{"error":{"code":"Denied","message":"Given value does not match HMAC header structure."}}

The access keys and names come from this command:

PS C:\Users\manko> az communication list-key --name scotttestingsms --resource-group smsresourcegroup
{
  "primaryConnectionString": "endpoint=https://scotttestingsms.communication.azure.com/;accesskey=E7lrdL/yyg/snSO++rbaZMEUF/bC5/0R9XBVGcFclt3fEN/MpWRb5kHB9t59NLtek9xsUYXHyAXxxxxxxx==",
  "primaryKey": "E7lrdL/yyg/snSO++rbaZMEUF/bC5/0R9XBVGcFclt3fEN/MpWRb5kHB9t59NLtek9xsUYXHyAXxxxxxx==",
  "secondaryConnectionString": "endpoint=https://scotttestingsms.communication.azure.com/;accesskey=IIK094eGVfkNG0uFii/32j+HVsEHJ4/QUOx06TVsqwLub7A/cv1AKKnkkZQbKiJKMn/KRx9o1biWQ5txxxxxx==",
  "secondaryKey": "IIK094eGVfkNG0uFii/32j+HVsEHJ4/QUOx06TVsqwLub7A/cv1AKKnkkZQbKiJKMn/KRx9o1biWQ5txxxxxx=="
}


Solution 1:[1]

It took quite some time of trial and error, but the following code works. See https://docs.microsoft.com/en-us/azure/communication-services/tutorials/hmac-header-tutorial for more information.

use GuzzleHttp\Client;

    public static function send(array $phoneNumbers, string $message)
    {
        $body = [
            'from' => config("azure.sms_phone_number"),
            'message' => $message,
            'smsRecipients' => array_map(fn ($num) => ['to' => $num], $phoneNumbers)
        ];

        $endpoint = parse_url(config("azure.sms_endpoint"));

        $headers = [
            'Date' => gmdate("D, d M Y H:i:s T"),
            'host' => $endpoint['host'],
            'x-ms-content-sha256' => base64_encode(hash('sha256', json_encode($body), true)),
        ];

        $stringToSign = utf8_encode(implode("\n", [
            "POST",
            $endpoint['path'] . "?" . $endpoint['query'],
            implode(";", array_values($headers))
        ]));

        $headers['Authorization'] = implode("&", [
            "HMAC-SHA256 SignedHeaders=" . implode(";", array_keys($headers)),
            'Signature=' . base64_encode(hash_hmac('sha256', $stringToSign, base64_decode(config("azure.sms_key")), true))
        ]);

        $client = new Client();  // <-- this is guzzle

        $response = $client->post(config("azure.sms_endpoint"), [
            'headers' => $headers,
            'json' => $body
        ]);

    }

You only need three pieces of data.

  1. config("azure.sms_phone_number") is the originating phone number. It must be in E.164 format, e.g. "+12024561414"
  2. config("azure.sms_endpoint") is the full endpoint, e.g. "https://test.communication.azure.com/sms?api-version=2021-03-07"
  3. config("azure.sms_key") is the application key copied right off the azure portal in base64, e.g. "E7lrdL/yyg/snSO++rbaZMEUF/d1G/0R9XBVGch0tq3xxxxxxxxxxxx=="

Solution 2:[2]

"Given value does not match HMAC header structure."

The error says that the header you are passing has some issues with the structure format.

  • Please follow the below structure for constructing the header

The Communication Header Authorization should be in the below format:

`Authorization`: **HMAC-SHA256** `Credential`=<value>&`SignedHeaders`=<value>&`Signature`=<value>  
  • Credential - HMAC-SHA256 Credentials
  • SignedHeaders - In signedHeaders we have to pass the below values
var signedHeaders = "x-ms-date;host;x-ms-content-sha256;`Content-Type`;`Accept`";

HTTP request header names, separated by semicolons, required to sign the request. These HTTP headers must be correctly provided with the request as well. Don't use white spaces.

x-ms-date - x-ms-date or Date you have to pass.

  • The correct format for x-ms-date header is Mon, 14Mar 2022 23:39:12 GMT.

  • host - your host Name

  • x-ms-content-sha256 - base64 encoded SHA256 hash of the request body. It must be provided even if there is no body. base64_encode(SHA256(body))

  • Content-Type - You have to pass the content-type

  • Accept - You have to pass the <MIME_type>/<MIME_subtype>

  • Signature - Base64 encoded HMACSHA256 hash of the String-To-Sign. It uses the access key identified by Credential. base64_encode(HMACSHA256(String-To-Sign, Secret))

String-To-Sign - HTTP_METHOD + '\n' + path_and_query + '\n' + signed_headers_values

  • HTTP_METHOD - Uppercase HTTP method name used with the request. For more information(GET,POST, PUT, DELETE )
  • path_and_query - Concatenation of Request absolute URI PATH & Query_String.
  • signed_headers_values - Semicolon-separated values of all HTTP request headers listed in SignedHeaders

Please refer the example in MSDoc which you have provided.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mankowitz
Solution 2 HarshithaVeeramalla-MT