Skip to content

Commit 7ab304a

Browse files
authored
Merge pull request #207188 from maximrytych-ms/feature/maximrytych/add-hmac-header-sample-for-python
Add HMAC header tutorial sample for Python
2 parents 8311ae1 + a297422 commit 7ab304a

File tree

4 files changed

+209
-3
lines changed

4 files changed

+209
-3
lines changed

articles/communication-services/toc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ items:
120120
href: tutorials/events-playbook.md
121121
- name: Use Postman to send SMS messages
122122
href: tutorials/postman-tutorial.md
123-
- name: Sign an HTTP request with HMAC using C#
123+
- name: Sign an HTTP request with HMAC
124124
href: tutorials/hmac-header-tutorial.md
125125
- name: Build an authentication service using Azure Functions
126126
href: tutorials/trusted-service-tutorial.md

articles/communication-services/tutorials/hmac-header-tutorial.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,23 @@ ms.date: 06/30/2021
1111
ms.topic: tutorial
1212
ms.service: azure-communication-services
1313
ms.subservice: identity
14+
zone_pivot_groups: acs-programming-languages-csharp-python
1415
---
1516

1617
# Sign an HTTP request
1718

1819
In this tutorial, you'll learn how to sign an HTTP request with an HMAC signature.
1920

20-
[!INCLUDE [Sign an HTTP request C#](./includes/hmac-header-csharp.md)]
21+
>[!NOTE]
22+
>We strongly encourage to use [Azure SDKs](https://github.com/Azure/azure-sdk). Approach described here is a fallback option for cases when Azure SDKs can't be used for any reason.
23+
24+
::: zone pivot="programming-language-csharp"
25+
[!INCLUDE [Sign an HTTP request with C#](./includes/hmac-header-csharp.md)]
26+
::: zone-end
27+
28+
::: zone pivot="programming-language-python"
29+
[!INCLUDE [Sign an HTTP request with Python](./includes/hmac-header-python.md)]
30+
::: zone-end
2131

2232
## Clean up resources
2333

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
```

articles/zone-pivot-groups.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,15 @@ groups:
18291829
title: C#
18301830
- id: programming-language-python
18311831
title: Python
1832+
# Owner: maximrytych-ms
1833+
- id: acs-programming-languages-csharp-python
1834+
title: Programming languages
1835+
prompt: Choose a programming language
1836+
pivots:
1837+
- id: programming-language-csharp
1838+
title: C#
1839+
- id: programming-language-python
1840+
title: Python
18321841
# Owner: jorgegarc
18331842
- id: acs-xamarin-react
18341843
title: Cross-platform pivots
@@ -1848,4 +1857,4 @@ groups:
18481857
- id: powershell
18491858
title: PowerShell
18501859
- id: bicep
1851-
title: Bicep
1860+
title: Bicep

0 commit comments

Comments
 (0)