[Actualité] Python : générer un JSON Web Token pour ensuite s'authentifier sur un serveur web
par
, 09/07/2023 à 22h40 (5710 Affichages)
I. Introduction
JSON Web Token (JWT) est un standard ouvert défini dans le document RFC 75191. Il permet l'échange sécurisé de jetons (tokens) entre plusieurs parties.
Cette sécurité de l’échange se traduit par la vérification de l'intégrité et de l'authenticité des données. Elle s’effectue par l'algorithme HMAC ou RSA.
On souhaite dans ce billet montrer comment créer un JWT en Python, pour nous permettre ensuite de nous authentifier sur un serveur web et pouvoir ainsi y récupérer des données.
II. Structure d'un JSON Web Token
Un JSON Web Token ou JWT est composé de trois parties :
- Un en-tête (header), utilisé pour décrire le jeton. Il s'agit d'un objet JSON.
- Une charge utile (payload) qui représente les informations embarquées dans le jeton. Il s'agit également d'un objet JSON.
- Une signature numérique.
II-A. En-tête
Il comporte au moins 2 paramètres :
- typ: spécifie le type de jeton. On passe toujours la valeur "JWT" à ce paramètre.
- alg: algorithme utilisé pour signer le jeton. On choisira dans notre cas la valeur "HS256" pour ce paramètre, et donc on utilisera l'algorithme HMAC SHA-256 pour signer le jeton.
Ce qui donne pour le header :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 { "typ": "JWT", "alg": "HS256" }
II-B. Charge utile
La partie charge utile ou payload a dans notre cas comme paramètres :
- name: nom de l'émetteur du jeton.
- iat : date de création du jeton. On prend l'heure Unix, c'est à dire le nombre de secondes écoulées depuis le 1ᵉʳ janvier 1970 00:00:00 UTC.
On peut ainsi avoir pour le payload :
Code JavaScript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 { "name": "John Bradley", "iat": 1688462212 }
Si vous souhaitez avoir la liste des paramètres standards utilisés pour la charge utile, je vous invite à consulter la section Champs standards (claims) de la page Wikipedia JSON Web Token.
II-C. Signature
1. Pour obtenir la signature, il faut tout d'abord encoder séparément l'en-tête et la charge utile avec base64url, puis les concaténer en les séparant par un point :
base64UrlEncode(header) + "." + base64UrlEncode(payload)
2. On utilise ensuite l'algorithme HS256 défini dans l'en-tête pour obtenir la signature de ce résultat. La clé utilisée par l'algorithme correspond à la clé privée :
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), clé_privée )
Cette signature est ajoutée de la même manière au résultat obtenu en 1. (encodée et séparée par un point).
La chaîne complète obtenue représente le JWT recherché.
Vous pouvez d'ailleurs créer un JWT en ligne sur le site https://jwt.io/.
On obtient ainsi en reprenant notre exemple et en choisissant "ma super clé secrète" comme clé privée :
Note : on veillera à conserver la clé secrète de façon extrêmement sécurisée comme il est indiqué dans la section Vulnérabilités de la page Wikipedia JSON Web Token.
III. Implémentation en Python
III-A. Encodage et décodage d'un JSON Web Token
La librairie PyJWT permet d'encoder et de décoder un JSON Web Token.
Installation de la librairie :
pip install pyjwt
La fonction encode de la librairie PyJWT effectue l'encodage d'un JWT à partir de la charge utile, de la clé secrète et de l'algorithme choisi.
Exemple d'utilisation :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 import jwt >>> payload = {"name": "John Bradley", "iat": 1688462212} >>> encoded_jwt = jwt.encode(payload, "ma super clé secrète", algorithm="HS256") >>> print(encoded_jwt) eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBCcmFkbGV5IiwiaWF0IjoxNjg4NDYyMjEyfQ.a8LZrE5l3G2BnQbA6241QSet_5JWdZJ3VKYPnrqASn4 >>> jwt.decode(encoded_jwt, 'ma super clé secrète', algorithms="HS256") {'name': 'John Bradley', 'iat': 1688462212}
Note : il peut arriver que la clé soit encodée en Base64, il faut alors la décoder avant de la transmettre à la fonction encode.
III-B. Récupération de données provenant d'un serveur web
La librairie Requests permet d'envoyer très simplement des requêtes HTTP à un serveur web.
Installation de la librairie :
pip install requests
Exemple d'utilisation :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 >>> import requests >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.status_code 200 >>> r.encoding 'utf-8' >>> r.text '{"type":"User"...' >>> r.json() {'private_gists': 419, 'total_private_repos': 77, ...}
On doit dans notre cas récupérer des données provenant d'un serveur web à partir d'une clé de souscription et d'un JWT obtenu à l'aide de la fonction encode.
Le header de la requête HTTP devrait ressembler à cela :
Ocp-Apim-Subscription-Key: <clé de souscription>
Authorization: Bearer <JWT Token>
On donne maintenant le code complet de la fonction :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 def get_data_product(api_key, api_token, product_id): # récupération de données provenant d'un serveur web à partir de la clé secrète, du jeton et de l'identifiant du produit recherché try: # composition de l'en-tête de la requête avec la clé de souscription et le JWT headers = {'Ocp-Apim-Subscription-Key': api_key, 'Authorization': 'Bearer {token}'.format(token=api_token)} # exécution d'une requête get sur le serveur web pour récupérer les données sur le produit r = requests.get('https://webplatform.com/read/api/products/{product_id}'.format(product_id=product_id), headers=headers) # on renvoie les données sur le produit au format json return r.json() # gestion de l'erreur except requests.exceptions.RequestException as e print(e)
L'en-tête de la requête est donc bien composé de la clé de souscription Apim et du JWT :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part headers = {'Ocp-Apim-Subscription-Key': api_key, 'Authorization': 'Bearer {token}'.format(token=api_token)}
Module complet de test :
Code Python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 import time # module contenant la fonction time() qui renvoie l'heure Unix import jwt # librairie utilisée pour encoder le JWT à partir de la charge utile, de la clé secrète et de l'algorithme choisi import requests # librairie permettant d'exécuter des requêtes HTTP def get_data_product(api_key, api_token, product_id): # récupération de données provenant d'un serveur web à partir de la clé secrète, du jeton et de l'identifiant du produit recherché try: # composition de l'en-tête de la requête avec la clé de souscription et le JWT headers = {'Ocp-Apim-Subscription-Key': api_key, 'Authorization': 'Bearer {token}'.format(token=api_token)} # exécution d'une requête get sur le serveur web pour récupérer les données sur le produit r = requests.get('https://webplatform.com/read/api/products/{product_id}'.format(product_id=product_id), headers=headers) # on renvoie les données sur le produit au format json return r.json() # gestion de l'erreur except requests.exceptions.RequestException as e: print(e) # heure Unix de création du jeton : nombre de secondes écoulées depuis le 1ᵉʳ janvier 1970 00:00:00 UTC. iat = int(time.time()) # charge utile payload = {"name": 'John Bradley', "iat": iat} # clé secrète api_key = "ma super clé secrète" # affiche l'en-tête, la charge utile et la clé secrète print("Header") print({"typ": "JWT", "alg": "HS256"});print() print("Payload") print(payload); print() print("Clé secrète") print(api_key); print() # encodage du JWT à partir de la charge utile, de la clé et de l'algorithme api_token = jwt.encode( payload=payload, key=api_key, algorithm='HS256') # affiche le résultat de l'encodage du jeton print("Résultat de l'encodage du JWT") print(api_token); print() # récupération des données à partir de la clé de souscription (api_key), du JWT (api_token), et de l'identifiant du produit data_product = get_data_product(api_key, api_token, 'p173795') # affiche les informations sur le produit print(data_product)
Le code affiche pour la partie génération du JWT :
Note : les paramètres du JWT et de l'en-tête de la requête doivent bien sûr être adaptés à chaque cas.
IV. Conclusion
Après avoir décrit la structure d'un JWT et les différentes étapes nécessaires pour le générer, nous avons pu proposer une implémentation en Python avec une fonction permettant de récupérer des données provenant d'un serveur web.
Chacun pourra ensuite librement adapter les paramètres du JWT et de la requête à ses besoins.
Sources :
https://fr.wikipedia.org/wiki/JSON_Web_Token
https://datatracker.ietf.org/doc/html/rfc7519
https://jwt.io/
https://fr.wikipedia.org/wiki/Base64
https://pyjwt.readthedocs.io/en/latest/
https://requests.readthedocs.io/en/latest/