|
| 1 | +--- |
| 2 | +title: Sign an HTTP request with Python |
| 3 | +description: This tutorial explains the Python version of signing an HTTP request with an HMAC signature for Azure Communication Services. |
| 4 | +author: maximrytych-ms |
| 5 | +manager: anitharaju |
| 6 | +services: azure-communication-services |
| 7 | + |
| 8 | +ms.author: maximrytych |
| 9 | +ms.date: 08/05/2022 |
| 10 | +ms.topic: include |
| 11 | +ms.service: azure-communication-services |
| 12 | +--- |
| 13 | +## Prerequisites |
| 14 | + |
| 15 | +Before you get started, make sure to: |
| 16 | + |
| 17 | +- Create an Azure account with an active subscription. For details, see [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F). |
| 18 | +- Download and install [Python](https://www.python.org/). |
| 19 | +- Download and install [Visual Studio Code](https://code.visualstudio.com/) or other IDE that supports Python. |
| 20 | +- Create an Azure Communication Services resource. For details, see [Create an Azure Communication Services resource](../../quickstarts/create-communication-resource.md). You'll need your **resource_endpoint_name** and **resource_endpoint_secret** for this tutorial. |
| 21 | + |
| 22 | +## Sign an HTTP request with Python |
| 23 | + |
| 24 | +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: |
| 25 | + |
| 26 | +``` |
| 27 | +Authorization: "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=<hmac-sha256-signature>" |
| 28 | +``` |
| 29 | + |
| 30 | +The `hmac-sha256-signature` consists of: |
| 31 | + |
| 32 | +- HTTP verb (for example, `GET` or `PUT`) |
| 33 | +- HTTP request path |
| 34 | +- x-ms-date |
| 35 | +- Host |
| 36 | +- x-ms-content-sha256 |
| 37 | + |
| 38 | +## Setup |
| 39 | + |
| 40 | +The following steps describe how to construct the authorization header. |
| 41 | + |
| 42 | +### Create a new Python script |
| 43 | + |
| 44 | +Open Visual Studio Code or other IDE or editor of your choice and create a new file named `SignHmacTutorial.py`. Save this file to a known folder. |
| 45 | + |
| 46 | +## Add necessary imports |
| 47 | + |
| 48 | +Update the `SignHmacTutorial.py` script with the following code to begin. |
| 49 | + |
| 50 | +```python |
| 51 | +import base64 |
| 52 | +import hashlib |
| 53 | +import hmac |
| 54 | +import json |
| 55 | +from datetime import datetime, timezone |
| 56 | +from urllib import request |
| 57 | +``` |
| 58 | + |
| 59 | +## Prepare data for the request |
| 60 | + |
| 61 | +For this example, we'll sign a request to create a new identity by using the Communication Services Authentication API [(version `2021-03-07`)](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/communication/data-plane/Identity/stable/2021-03-07). |
| 62 | + |
| 63 | +Add the following code to the `SignHmacTutorial.py` script. |
| 64 | + |
| 65 | +- Replace `resource_endpoint_name` with your real resource endpoint name value. This value can be found in Overview section of your Azure Communication Services resource. It's the value of "Endpoint" after "https://". |
| 66 | +- Replace `resource_endpoint_secret` with your real resource endpoint secret value. This value can be found in Keys section of your Azure Communication Services resource. It's the value of "Key" - either primary or secondary. |
| 67 | + |
| 68 | +```python |
| 69 | +host = "resource_endpoint_name" |
| 70 | +resource_endpoint = f"https://{host}" |
| 71 | +path_and_query = "/identities?api-version=2021-03-07" |
| 72 | +secret = "resource_endpoint_secret" |
| 73 | + |
| 74 | +# Create a uri you are going to call. |
| 75 | +request_uri = f"{resource_endpoint}{path_and_query}" |
| 76 | + |
| 77 | +# Endpoint identities?api-version=2021-03-07 accepts list of scopes as a body. |
| 78 | +scopes = ["chat"] |
| 79 | + |
| 80 | +serialized_body = json.dumps(scopes) |
| 81 | +content = serialized_body.encode("utf-8") |
| 82 | +``` |
| 83 | + |
| 84 | +## Create a content hash |
| 85 | + |
| 86 | +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 `SignHmacTutorial.py` script. |
| 87 | + |
| 88 | +```python |
| 89 | +def compute_content_hash(content): |
| 90 | + sha_256 = hashlib.sha256() |
| 91 | + sha_256.update(content) |
| 92 | + hashed_bytes = sha_256.digest() |
| 93 | + base64_encoded_bytes = base64.b64encode(hashed_bytes) |
| 94 | + content_hash = base64_encoded_bytes.decode('utf-8') |
| 95 | + return content_hash |
| 96 | +``` |
| 97 | + |
| 98 | +## Compute a signature |
| 99 | + |
| 100 | +Use the following code to create a method for computing your HMAC signature. |
| 101 | + |
| 102 | +```python |
| 103 | +def compute_signature(string_to_sign, secret): |
| 104 | + decoded_secret = base64.b64decode(secret) |
| 105 | + encoded_string_to_sign = string_to_sign.encode('ascii') |
| 106 | + hashed_bytes = hmac.digest(decoded_secret, encoded_string_to_sign, digest=hashlib.sha256) |
| 107 | + encoded_signature = base64.b64encode(hashed_bytes) |
| 108 | + signature = encoded_signature.decode('utf-8') |
| 109 | + return signature |
| 110 | +``` |
| 111 | + |
| 112 | +## Get current UTC timestamp according to the RFC1123 standard |
| 113 | + |
| 114 | +Use the following code to get desired date format independent of locale settings. |
| 115 | + |
| 116 | +```python |
| 117 | +def format_date(dt): |
| 118 | + days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
| 119 | + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
| 120 | + utc = dt.utctimetuple() |
| 121 | + |
| 122 | + return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format( |
| 123 | + days[utc.tm_wday], |
| 124 | + utc.tm_mday, |
| 125 | + months[utc.tm_mon-1], |
| 126 | + utc.tm_year, |
| 127 | + utc.tm_hour, |
| 128 | + utc.tm_min, |
| 129 | + utc.tm_sec) |
| 130 | +``` |
| 131 | + |
| 132 | +## Create an authorization header string |
| 133 | + |
| 134 | +We'll now construct the string that we'll add to our authorization header. |
| 135 | + |
| 136 | +1. Prepare values for the headers to be signed. |
| 137 | + 1. Specify the current timestamp using the Coordinated Universal Time (UTC) timezone. |
| 138 | + 1. Get the request authority (DNS host name or IP address and the port number). |
| 139 | + 1. Compute a content hash. |
| 140 | +1. Prepare a string to sign. |
| 141 | +1. Compute the signature. |
| 142 | +1. Concatenate the string, which will be used in the authorization header. |
| 143 | + |
| 144 | +Add the following code to the `SignHmacTutorial.py` script. |
| 145 | + |
| 146 | +```python |
| 147 | +# Specify the 'x-ms-date' header as the current UTC timestamp according to the RFC1123 standard |
| 148 | +utc_now = datetime.now(timezone.utc) |
| 149 | +date = format_date(utc_now) |
| 150 | +# Compute a content hash for the 'x-ms-content-sha256' header. |
| 151 | +content_hash = compute_content_hash(content) |
| 152 | + |
| 153 | +# Prepare a string to sign. |
| 154 | +string_to_sign = f"POST\n{path_and_query}\n{date};{host};{content_hash}" |
| 155 | +# Compute the signature. |
| 156 | +signature = compute_signature(string_to_sign, secret) |
| 157 | +# Concatenate the string, which will be used in the authorization header. |
| 158 | +authorization_header = f"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={signature}" |
| 159 | +``` |
| 160 | + |
| 161 | +## Add headers |
| 162 | + |
| 163 | +Use the following code to add the required headers. |
| 164 | + |
| 165 | +```python |
| 166 | +request_headers = {} |
| 167 | + |
| 168 | +# Add a date header. |
| 169 | +request_headers["x-ms-date"] = date |
| 170 | + |
| 171 | +# Add content hash header. |
| 172 | +request_headers["x-ms-content-sha256"] = content_hash |
| 173 | + |
| 174 | +# Add authorization header. |
| 175 | +request_headers["Authorization"] = authorization_header |
| 176 | +``` |
| 177 | + |
| 178 | +## Test the client |
| 179 | + |
| 180 | +Call the endpoint and check the response. |
| 181 | + |
| 182 | +```python |
| 183 | +req = request.Request(request_uri, content, request_headers, method='POST') |
| 184 | +with request.urlopen(req) as response: |
| 185 | + response_string = json.load(response) |
| 186 | +print(response_string) |
| 187 | +``` |
0 commit comments