Tutorial: Sign an HTTP request

In this tutorial, you learn how to sign an HTTP request with a hash-based message authentication code (HMAC) signature.

Note

We strongly encourage you to use Azure SDKs. The approach described here is a fallback option for cases when Azure SDKs can't be used for any reason.

In this tutorial, you learn how to:

  • Create a request message.
  • Create a content hash.
  • Compute a signature.
  • Create an authorization header string.
  • Add headers.

Prerequisites

Before you get started, make sure to:

Sign an HTTP request with C#

Access key authentication uses a shared secret key to generate an HMAC signature for each HTTP request. This signature is generated with the SHA256 algorithm and is sent in the Authorization header by using the HMAC-SHA256 scheme. For example:

Authorization: "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=<hmac-sha256-signature>"

The hmac-sha256-signature consists of:

  • HTTP verb (for example, GET or PUT)
  • HTTP request path
  • x-ms-date
  • Host
  • x-ms-content-sha256

Set up the authorization header

The following steps describe how to construct the authorization header.

Create a new C# application

In a console window, such as cmd, PowerShell, or Bash, use the dotnet new command to create a new console app with the name SignHmacTutorial. This command creates a simple "Hello World" C# project with a single source file: Program.cs.

dotnet new console -o SignHmacTutorial

Change your directory to the newly created app folder. Use the dotnet build command to compile your application.

cd SignHmacTutorial
dotnet build

Install the package

Install the package Newtonsoft.Json that's used for body serialization.

dotnet add package Newtonsoft.Json

Update the Main method declaration to support async code. Use the following code to begin.

using System;
using System.Globalization;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace SignHmacTutorial
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Azure Communication Services - Sign an HTTP request Tutorial");
            // Tutorial code goes here.
        }
    }
}

Create a request message

For this example, you sign a request to create a new identity by using the Communication Services Authentication API (version 2021-03-07).

Add the following code to the Main method.

string resourceEndpoint = "resourceEndpoint";
// Create a uri you are going to call.
var requestUri = new Uri($"{resourceEndpoint}/identities?api-version=2021-03-07");
// Endpoint identities?api-version=2021-03-07 accepts list of scopes as a body
var body = new
    {
        createTokenWithScopes = new[] { "chat" }
    };

var serializedBody = JsonConvert.SerializeObject(body);

var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri)
{
    Content = new StringContent(serializedBody, Encoding.UTF8, "application/json")
};

Replace resourceEndpoint with your real resource endpoint value.

Create a content hash

The content hash is a part of your HMAC signature. Use the following code to compute the content hash. You can add this method to Program.cs under the Main method.

static string ComputeContentHash(string content)
{
    using var sha256 = SHA256.Create();
    byte[] hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(content));
    return Convert.ToBase64String(hashedBytes);
}

Compute a signature

Use the following code to create a method for computing your HMAC signature.

static string ComputeSignature(string stringToSign)
{
    string secret = "resourceAccessKey";
    using var hmacsha256 = new HMACSHA256(Convert.FromBase64String(secret));
    var bytes = Encoding.UTF8.GetBytes(stringToSign);
    var hashedBytes = hmacsha256.ComputeHash(bytes);
    return Convert.ToBase64String(hashedBytes);
}

Replace resourceAccessKey with an access key of your real Communication Services resource.

Create an authorization header string

Now you construct the string that you add to your authorization header.

  1. Prepare values for the headers to be signed.
    1. Specify the current timestamp by using the Coordinated Universal Time (UTC) timezone.
    2. Get the request authority. Use the Domain Name System (DNS) host name or IP address and the port number.
    3. Compute a content hash.
  2. Prepare a string to sign.
  3. Compute the signature.
  4. Concatenate the string, which is used in the authorization header.

Add the following code to the Main method.

// Specify the 'x-ms-date' header as the current UTC timestamp according to the RFC1123 standard.
var date = DateTimeOffset.UtcNow.ToString("r", CultureInfo.InvariantCulture);
// Get the host name corresponding with the 'host' header.
var host = requestUri.Authority;
// Compute a content hash for the 'x-ms-content-sha256' header.
var contentHash = ComputeContentHash(serializedBody);

// Prepare a string to sign.
var stringToSign = $"POST\n{requestUri.PathAndQuery}\n{date};{host};{contentHash}";
// Compute the signature.
var signature = ComputeSignature(stringToSign);
// Concatenate the string, which will be used in the authorization header.
var authorizationHeader = $"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={signature}";

Add headers to requestMessage

Use the following code to add the required headers to your requestMessage parameter.

// Add a date header.
requestMessage.Headers.Add("x-ms-date", date);

// Add a host header.
// In C#, the 'host' header is added automatically by the 'HttpClient'. However, this step may be required on other platforms such as Node.js.

// Add a content hash header.
requestMessage.Headers.Add("x-ms-content-sha256", contentHash);

// Add an authorization header.
requestMessage.Headers.Add("Authorization", authorizationHeader);

Test the client

Call the endpoint by using HttpClient, and check the response.

HttpClient httpClient = new HttpClient
{
    BaseAddress = requestUri
};
var response = await httpClient.SendAsync(requestMessage);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);

Prerequisites

Before you get started, make sure to:

  • Create an Azure account with an active subscription. If you don't have an Azure subscription, see Create an account for free.
  • Download and install Python.
  • Download and install Visual Studio Code or another integrated development environment (IDE) that supports Python.
  • Create an Azure Communication Services resource. If you don't have a resource, see Create a Communication Services resource. You need your resource_endpoint_name and resource_endpoint_secret parameters for this tutorial.

Sign an HTTP request with Python

Access key authentication uses a shared secret key to generate an HMAC signature for each HTTP request. This signature is generated with the SHA256 algorithm and is sent in the Authorization header by using the HMAC-SHA256 scheme. For example:

Authorization: "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=<hmac-sha256-signature>"

The hmac-sha256-signature consists of:

  • HTTP verb (for example, GET or PUT)
  • HTTP request path
  • x-ms-date
  • Host
  • x-ms-content-sha256

Set up the authorization header

The following steps describe how to construct the authorization header.

Create a new Python script

Open Visual Studio Code or another IDE or editor of your choice. Create a new file named sign_hmac_tutorial.py. Save this file to a known folder.

Add necessary imports

Update the sign_hmac_tutorial.py script with the following code to begin.

import base64
import hashlib
import hmac
import json
from datetime import datetime, timezone
from urllib import request

Prepare data for the request

For this example, you sign a request to create a new identity by using the Communication Services Authentication API (version 2021-03-07).

Add the following code to the sign_hmac_tutorial.py script.

  • Replace resource_endpoint_name with your real resource endpoint name value. You can find this value in the Overview section of your Communication Services resource. It's the value of Endpoint after https://.
  • Replace resource_endpoint_secret with your real resource endpoint secret value. You can find this value in the Keys section of your Communication Services resource. It's the value of Key, which is either primary or secondary.
host = "resource_endpoint_name"
resource_endpoint = f"https://{host}"
path_and_query = "/identities?api-version=2021-03-07"
secret = "resource_endpoint_secret"

# Create a uri you are going to call.
request_uri = f"{resource_endpoint}{path_and_query}"

# Endpoint identities?api-version=2021-03-07 accepts the list of scopes as a body.
body = { "createTokenWithScopes": ["chat"] }

serialized_body = json.dumps(body)
content = serialized_body.encode("utf-8")

Create a content hash

The content hash is a part of your HMAC signature. Use the following code to compute the content hash. You can add this method to the sign_hmac_tutorial.py script.

def compute_content_hash(content):
    sha_256 = hashlib.sha256()
    sha_256.update(content)
    hashed_bytes = sha_256.digest()
    base64_encoded_bytes = base64.b64encode(hashed_bytes)
    content_hash = base64_encoded_bytes.decode('utf-8')
    return content_hash

Compute a signature

Use the following code to create a method for computing your HMAC signature.

def compute_signature(string_to_sign, secret):
    decoded_secret = base64.b64decode(secret)
    encoded_string_to_sign = string_to_sign.encode('utf-8')
    hashed_bytes = hmac.digest(decoded_secret, encoded_string_to_sign, digest=hashlib.sha256)
    encoded_signature = base64.b64encode(hashed_bytes)
    signature = encoded_signature.decode('utf-8')
    return signature

Get a current UTC timestamp according to the RFC1123 standard

Use the following code to get the date format that you want that's independent of locale settings.

def format_date(dt):
    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
    utc = dt.utctimetuple()

    return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format(
    days[utc.tm_wday],
    utc.tm_mday,
    months[utc.tm_mon-1],
    utc.tm_year,
    utc.tm_hour, 
    utc.tm_min, 
    utc.tm_sec)

Create an authorization header string

Now you construct the string that you add to your authorization header.

  1. Prepare values for the headers to be signed.
    1. Specify the current timestamp by using the Coordinated Universal Time (UTC) timezone.
    2. Get the request authority. Use the Domain Name System (DNS) host name or IP address and the port number.
    3. Compute a content hash.
  2. Prepare a string to sign.
  3. Compute the signature.
  4. Concatenate the string, which is used in the authorization header.

Add the following code to the sign_hmac_tutorial.py script.

# Specify the 'x-ms-date' header as the current UTC timestamp according to the RFC1123 standard.
utc_now = datetime.now(timezone.utc)
date = format_date(utc_now)
# Compute a content hash for the 'x-ms-content-sha256' header.
content_hash = compute_content_hash(content)

# Prepare a string to sign.
string_to_sign = f"POST\n{path_and_query}\n{date};{host};{content_hash}"
# Compute the signature.
signature = compute_signature(string_to_sign, secret)
# Concatenate the string, which will be used in the authorization header.
authorization_header = f"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={signature}"

Add headers

Use the following code to add the required headers.

request_headers = {}

# Add a date header.
request_headers["x-ms-date"] = date

# Add a content hash header.
request_headers["x-ms-content-sha256"] = content_hash

# Add an authorization header.
request_headers["Authorization"] = authorization_header

# Add a content type header.
request_headers["Content-Type"] = "application/json"

Test the client

Call the endpoint and check the response.

req = request.Request(request_uri, content, request_headers, method='POST')
with request.urlopen(req) as response:
  response_string = json.load(response)
print(response_string)

Clean up resources

To clean up and remove a Communication Services subscription, delete the resource or resource group. Deleting the resource group also deletes any other resources associated with it. You can find out more about how to clean up Azure Communication Services resources and clean up Azure Functions resources.