Lire un jeton : JWT, signatures et clés
- Leçon 6
- 50 min
- Module 2 : Comprendre la connexion et les jetons
Leçon 6 sur 19 · Authentification, autorisations et applications multilocatairesLire un jeton : JWT, signatures et clés
1. Bases de l'identité et des autorisations
2. Comprendre la connexion et les jetons
3. Sessions et intégration à l'application
4. Contrôler l'accès aux ressources
5. Travail en arrière-plan et administration
6. Valider et documenter la conception
Ce que vous saurez faire
Ouvrez un jeton et lisez ses trois parties. Découvrez ce que prouve la signature du fournisseur et ses limites. Voyez comment l’API trouve la bonne clé publique pour la vérifier.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Le jeton d’accès et le jeton d’identité d’une connexion, présentés dans Flux d’application.
- SHA-256 comme empreinte d’un texte, présenté dans l’exemple PKCE de la même leçon.
Ce qu’il vous faut sur votre ordinateur
- Le projet après Flux d’application, avec
scripts/login.py. - Le fournisseur en cours d’exécution dans son propre terminal (
python -m idp), et une connexion enregistrée :.tokens/usr-sam.json. Si vous l’avez supprimée, exécutez de nouveaupython -m scripts.login --user usr-sam.
Cette leçon ajoute un fichier, scripts/token.py. Les jetons de cette leçon sont de vrais jetons du fournisseur d’exercice. Ils sont expirés, et une clé locale fictive les a signés. Ils ne donnent donc accès à rien. Les commandes ont été testées sur macOS (Apple silicon). Les commandes Windows sont écrites, mais n’ont pas été testées.
Un billet de banque
Section intitulée « Un billet de banque »Regardez un billet de banque. Tout le monde peut le lire : sa valeur, son numéro de série, la banque qui l’a émis. Rien n’y est secret.
Tout le monde peut aussi le vérifier. La banque publie les caractéristiques d’un vrai billet : le filigrane, le fil de sécurité, l’encre spéciale. Un commerçant vérifie ces caractéristiques sans consulter la banque. Mais seule la banque peut fabriquer un billet qui réussit cette vérification.
Un vrai billet prouve deux choses : la banque l’a fabriqué, et personne ne l’a modifié. Il ne prouve pas que la personne qui le présente en est propriétaire. Un billet volé s’utilise comme n’importe quel autre.
Le problème : une chaîne que tout le monde peut lire et modifier
Section intitulée « Le problème : une chaîne que tout le monde peut lire et modifier »Le jeton d’accès de Sam compte 732 caractères. Il semble incompréhensible. On peut donc facilement croire qu’il est secret ou que personne ne peut le modifier. Ni l’un ni l’autre n’est vrai.
Voici un vrai jeton d’accès provenant d’une connexion de Camille, du même type que celui de Sam. Il commence ainsi :
eyJhbGciOiJSUzI1NiIsImtpZCI6IjdIeGNCdGs0ZGpkcnNQekIiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg0MDAiLCJzdWIiOiJ1c3ItY2FtaWxsZSIsImF1ZCI6InRpY2tldC1hcGkiLCJleHAiOjE3OTE2MTE3NzAsIm5i…L’auteur y a changé un mot : usr-camille est devenu usr-grace, la propriétaire de Larkfield. Cette modification est un cas construit pour le cours. Un programme qui décode seulement le jeton lit maintenant :
sub "usr-grace"Si l’API faisait confiance à ce qu’elle lit, Camille serait maintenant Grace. L’API a donc besoin d’une chose que tout le monde peut lire, mais que personne ne peut falsifier : une signature.
L’idée : un JSON Web Token
Section intitulée « L’idée : un JSON Web Token »Les jetons du fournisseur sont des JSON Web Tokens (JWT, souvent prononcé « jot »). Un JWT comporte trois parties, séparées par deux points :
- L’en-tête : comment le jeton est signé.
algest l’algorithme,kidl’identifiant de clé,typle type de jeton. - Les revendications : ce que dit le jeton. Chaque revendication (claim) est un énoncé, composé d’un nom et d’une valeur, comme
"sub": "usr-camille". - La signature : la preuve du fournisseur qu’il a créé les deux premières parties, exactement telles qu’elles sont.
L’en-tête et les revendications sont du JSON, écrit en base64url. Base64url n’est pas du chiffrement. Il écrit simplement les octets sous forme de texte utilisable dans une adresse et dans un en-tête HTTP. Il prend les octets par groupes de 3, soit 24 bits. Il les écrit sous forme de 4 caractères de 6 bits chacun. Une valeur de 6 bits, de 0 à 63, choisit un caractère parmi A–Z, a–z, 0–9, - et _.
Voici un petit exemple où chaque bit est visible. L’en-tête de chaque jeton commence par les trois caractères {"a :
{ |
" |
a |
|
|---|---|---|---|
| Octets | 123 | 34 | 97 |
| Bits | 01111011 |
00100010 |
01100001 |
| Les mêmes 24 bits en groupes de 6 | 011110 = 30 |
110010 = 50 |
001001 = 9, puis 100001 = 33 |
| Caractères | e (30) |
y (50) |
J (9), puis h (33) |
Ainsi, {"a devient eyJh, et l’en-tête complet {"alg":"RS256"} devient eyJhbGciOiJSUzI1NiJ9. C’est pourquoi presque tous les JWT commencent par eyJ : c’est {" en base64url. Base64url supprime les caractères de remplissage = à la fin. Il utilise aussi - et _ là où base64 utilise + et /. Ces deux derniers caractères ont un sens dans une adresse.
Les revendications d’un jeton d’accès
Section intitulée « Les revendications d’un jeton d’accès »Copiez le script de lecture des jetons depuis l’instantané, puis décodez le jeton d’accès enregistré de Sam :
cp ../nextia-learning-labs/C23/snapshots/end-of-m02/scripts/token.py scripts/python -m scripts.token decode --user usr-samCopy-Item ..\nextia-learning-labs\C23\snapshots\end-of-m02\scripts\token.py scripts\python -m scripts.token decode --user usr-samVotre sortie affiche usr-sam et votre propre clé. Voici la même commande pour le jeton de Camille. L’auteur l’a exécutée en remplaçant --user usr-sam par le jeton complet :
A JWT is three parts, separated by dots: 74, 319 and 342 characters.1. header (base64url JSON): {"alg": "RS256", "kid": "7HxcBtk4djdrsPzB", "typ": "at+jwt"}2. claims (base64url JSON): iss "http://localhost:8400" sub "usr-camille" aud "ticket-api" exp 1791611770 (2026-10-10 05:56:10 UTC) nbf 1791611170 (2026-10-10 05:46:10 UTC) iat 1791611170 (2026-10-10 05:46:10 UTC) jti "14dafe2628fe4233891734b84ad77238" client_id "ticket-cli" scope "tickets:read tickets:write members:manage"3. signature: dX880F0-mK2-D7Yu... (256 bytes)Decoding is not checking: anyone can read these parts. Only the signature, checked withthe provider's public key '7HxcBtk4djdrsPzB', shows that the provider made them.Les noms des revendications sont courts par choix : chaque requête les transporte. Chacune a un sens précis :
| Revendication | Nom | Valeur de Camille | Ce qu’elle dit |
|---|---|---|---|
iss |
émetteur | http://localhost:8400 |
Quel fournisseur a créé le jeton |
sub |
sujet | usr-camille |
Qui le jeton concerne : un identifiant d’utilisateur stable qui ne change jamais |
aud |
audience | ticket-api |
À quelle API le jeton est destiné |
exp |
expiration | 5 h 56 min 10 s UTC | Non valide après ce moment |
nbf |
pas avant | 5 h 46 min 10 s UTC | Non valide avant ce moment |
iat |
émis à | 5 h 46 min 10 s UTC | Quand le fournisseur l’a créé |
jti |
identifiant de jeton | 14dafe26… |
Un identifiant unique pour ce jeton précis |
client_id |
ticket-cli |
Quelle application l’a demandé | |
scope |
trois portées | Ce que l’application peut faire pour Camille |
Les heures sont en secondes Unix : les secondes écoulées depuis le 1 janvier 1970, UTC. exp moins iat donne 1 791 611 770 − 1 791 611 170 = 600 secondes : 10 minutes, comme l’indiquait expires_in du fournisseur. Le jeton ne contient ni organisation ni rôle, selon la décision prise dans OAuth et OIDC.
La signature : facile à vérifier, impossible à falsifier sans la clé
Section intitulée « La signature : facile à vérifier, impossible à falsifier sans la clé »Le fournisseur signe avec RS256 : une signature RSA de l’empreinte SHA-256 des deux premières parties, header.claims. RSA utilise une paire de clés. La clé privée crée les signatures et ne quitte jamais le dossier .idp/keys/ du fournisseur. La clé publique vérifie les signatures, et tout le monde peut l’avoir. C’est comme le billet de banque : tout le monde peut vérifier, seule la banque peut fabriquer.
Un exemple simplifié montre les calculs avec des nombres assez petits pour les suivre. Les vraies clés utilisent des nombres de plus de 600 chiffres. Ceux-ci sont beaucoup trop petits pour être sûrs. La paire de clés simplifiée utilise n = 3 233, le nombre public e = 17 et le nombre privé d = 2 753 :
- Le fournisseur a une empreinte à signer. Disons que c’est le nombre 65.
- Il signe avec le nombre privé : 652753 mod 3 233 = 588. La signature est 588.
- L’API vérifie avec les nombres publics : 58817 mod 3 233 = 65. Le résultat égale l’empreinte, donc la signature correspond.
- Quelqu’un modifie les revendications, et leur empreinte est maintenant 66. L’ancienne signature donne toujours 65, et 65 ≠ 66 : refus.
- Pour signer 66, le faussaire a besoin du nombre privé. Sans lui, un essai comme 589 donne 2 838 à la vérification, pas 66.
(« mod 3 233 » signifie « le reste après la division par 3 233 ».) La vraie signature fait 256 octets : 342 caractères base64url. Changer une lettre dans les revendications change complètement l’empreinte, comme l’a montré l’exemple PKCE. L’ancienne signature ne correspond plus.
Description textuelle du diagramme
Suivez le jeton de gauche à droite. Le fournisseur signe header.claims avec sa clé privée. Le jeton passe par le navigateur ou un script, et tout le monde sur son trajet peut le lire. L’API lit le kid dans l’en-tête. Elle prend la clé publique avec cet identifiant dans la liste de clés du fournisseur, puis vérifie la signature. Une correspondance prouve deux choses : le fournisseur a créé le jeton, et aucun caractère de l’en-tête ou des revendications n’a changé depuis.
Les clés du fournisseur : un JWKS
Section intitulée « Les clés du fournisseur : un JWKS »Le fournisseur publie ses clés publiques sous forme de JWKS, un JSON Web Key Set, ou ensemble de clés Web JSON. Il le publie à l’adresse jwks_uri de son document de découverte. Demandez-le :
curl -s http://localhost:8400/jwks.json | python -m json.toolcurl.exe -s http://localhost:8400/jwks.json | python -m json.tool{ "keys": [ { "kty": "RSA", "key_ops": ["verify"], "n": "1Gad4mdVxOgzjDlPIplFn14xf3FjTSknTY8gXNTAC3UqJ7YwSKwuqPsfwYEmv1VI5-Dmx15c...", "e": "AQAB", "kid": "7HxcBtk4djdrsPzB", "use": "sig", "alg": "RS256" } ]}Chaque clé est un objet JSON. kty est le type de clé, RSA. n et e sont les deux nombres publics, comme 3 233 et 17 dans l’exemple simplifié. Mais n compte 2 048 bits. kid est l’identifiant de clé que les jetons indiquent dans leur en-tête. use et key_ops indiquent que cette clé vérifie les signatures. L’auteur a raccourci n ici; le vôtre compte environ 340 caractères. Cette liste vient de l’exécution qui a créé le jeton de Camille, donc son kid est 7HxcBtk4djdrsPzB. Le vôtre est l’identifiant de votre propre clé. Cette liste ne contient aucun nombre privé.
Pourquoi une liste et un identifiant de clé, plutôt qu’une seule clé? Parce que les clés changent. Un fournisseur remplace sa clé de signature de temps en temps, ou immédiatement si une clé a peut-être été divulguée. C’est la rotation des clés. Pendant une rotation, la liste contient l’ancienne clé et la nouvelle. Chaque jeton indique sa clé avec kid. Essayez :
-
Ajoutez une nouvelle clé. Sans
--activate, elle est publiée, mais ne signe pas encore :Fenêtre de terminal python -m idp rotate-keyNew key pHlo1LoZvVE4kFQ9: published. Activate it later: python -m idp activate-key pHlo1LoZvVE4kFQ9 -
Listez les clés :
Fenêtre de terminal python -m idp keysMy9O_SxNpIg4Ff10 published signs new tokenspHlo1LoZvVE4kFQ9 publishedOuvrez de nouveau
http://localhost:8400/jwks.json: il contient maintenant deux clés. -
Faites signer la nouvelle clé. Utilisez votre propre identifiant de clé de l’étape 1 :
Fenêtre de terminal python -m idp activate-key pHlo1LoZvVE4kFQ9 -
Connectez-vous de nouveau, puis décodez l’en-tête du nouveau jeton :
Fenêtre de terminal python -m scripts.login --user usr-sampython -m scripts.token decode --user usr-samL’en-tête indique maintenant
"kid": "pHlo1LoZvVE4kFQ9". La clé des jetons avec l’ancienkidreste publiée. Leur vérification réussit donc encore jusqu’à leur expiration. Plus tard,python -m idp retire-key <old kid>cesse de publier l’ancienne clé.
Pourquoi publier d’abord et signer ensuite? Une API garde la liste de clés en mémoire pendant un certain temps. Si une clé signe avant que l’API l’ait, l’API ne peut pas encore vérifier les jetons produits. Ce que signifie un jeton mesure la durée de cette situation.
Explorez : changez une chose dans un vrai jeton
Section intitulée « Explorez : changez une chose dans un vrai jeton »L’explorateur contient le vrai jeton d’accès de Camille. Changez une chose. Voyez quelle partie du jeton change et si la signature correspond encore. La colonne de droite montre toutes les vérifications de l’API. La prochaine leçon les explique une par une.
Explorer
Un jeton d'accès : ses trois parties et les vérifications de l'API
Prédisez d’abord : Quelqu'un remplace sub usr-camille par usr-grace après que le fournisseur a signé le jeton. Lesquelles des trois parties du jeton changent? La signature correspond-elle encore?
Le jeton d’accès exactement tel que le fournisseur l’a émis. Accepté : toutes les vérifications ont réussi. L’horloge de l’API dans cet explorateur : 2026-10-10 05:48:10 UTC, deux minutes après la connexion.
Le jeton
en-tête . revendications . signature · changé = diffère du jeton tel qu’émis
eyJhbGciOiJSUzI1NiIsImtpZCI6IjdIeGNCdGs0ZGpkcnNQekIiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg0MDAiLCJzdWIiOiJ1c3ItY2FtaWxsZSIsImF1ZCI6InRpY2tldC1hcGkiLCJleHAiOjE3OTE2MTE3NzAsIm5iZiI6MTc5MTYxMTE3MCwiaWF0IjoxNzkxNjExMTcwLCJqdGkiOiIxNGRhZmUyNjI4ZmU0MjMzODkxNzM0Yjg0YWQ3NzIzOCIsImNsaWVudF9pZCI6InRpY2tldC1jbGkiLCJzY29wZSI6InRpY2tldHM6cmVhZCB0aWNrZXRzOndyaXRlIG1lbWJlcnM6bWFuYWdlIn0.dX880F0-mK2-D7Yu94CnoeV7_dujRDIBmC25k1F8eZrPlXlHlLmWNsSNfYvx4dt9Rk2nfpoec4eZhuWYJGf7k9NgoyApre6hxt0jpysfR5RoWofmMlNWBaZWQaMYjiGPXhCqzhdoJc2qnSEMyzr6jnDAWAUiJn89ejMObwNozADY6v9sANTiyGzxa5Va9ggl9tDw0syBpZU7bzRQsxm-PBljrpLX-yCP8wZswpINMkrO6Z33Pd0xCszmQLwEebF_ExG5n_8U-2LcrRyd2DaeKXplDgNtO32tbhGLLTSz7xQlJStwZHyQ_Y4dEgQlrsBYWuglka4EXcpKab6-VxIbgw
alg | "RS256" |
|---|---|
kid | "7HxcBtk4djdrsPzB" |
typ | "at+jwt" |
iss | "http://localhost:8400" |
|---|---|
sub | "usr-camille" |
aud | "ticket-api" |
exp | 17916117702026-10-10 05:56:10 UTC |
nbf | 17916111702026-10-10 05:46:10 UTC |
iat | 17916111702026-10-10 05:46:10 UTC |
jti | "14dafe2628fe4233891734b84ad77238" |
client_id | "ticket-cli" |
scope | "tickets:read tickets:write members:manage" |
3. Signature : La vraie signature du fournisseur.
Les vérifications de l’API, dans l’ordre
- 1. Un JWT : trois parties base64urlréussie : l’en-tête et les revendications se décodent
- 2. Algorithme dans la liste d’autorisation (RS256)réussie : alg vaut "RS256"
- 3. Un jeton d’accès (typ at+jwt)réussie : typ vaut "at+jwt"
- 4. Identifiant de clé publié par le fournisseurréussie : kid "7HxcBtk4djdrsPzB"
- 5. La signature correspond à cette clé publiqueréussie : correspond à la clé publique de ce kid
- 6. Revendications obligatoires présentesréussie : exp, iat, iss, aud, sub
- 7. Entre nbf et exp (tolérance de 30 s)réussie : exp est dans 480 s
- 8. L’émetteur est notre fournisseurréussie : iss vaut "http://localhost:8400"
- 9. L’audience est ticket-apiréussie : aud vaut "ticket-api"
GET /v1/me → 200 OK
L’API accepte le jeton : l’appelant est le sub du jeton.
Description textuelle de l’explorateur
L’explorateur montre un vrai jeton d’accès du fournisseur d’exercice : une connexion de usr-camille, de 737 caractères. Il est expiré et a été signé avec une clé simulée locale. Le jeton apparaît en trois parties colorées : en-tête, revendications et signature. En dessous figurent l’en-tête et les revendications décodés, ainsi qu’une note sur la signature. À droite, les neuf vérifications de l’API sont listées dans l’ordre, puis vient la réponse de l’API à GET /v1/me. Dans l’explorateur, l’horloge de l’API indique deux minutes après la connexion. Le jeton inchangé y est donc valide.
Tel qu’il a été émis, le jeton réussit chaque vérification et l’API répond 200. Avec « sub : usr-grace, edited » (sub : usr-grace, modifié), seule la partie du milieu, les revendications, change. L’en-tête et la signature restent identiques. La vérification de signature, la cinquième, échoue avec bad_signature, car l’ancienne signature a été créée pour les anciennes revendications.
Les autres boutons changent chacun une chose, et une vérification refuse chaque changement. La vérification de l’audience refuse aud billing-api (wrong_audience). La vérification du temps refuse exp il y a 5 minutes (expired) et nbf dans 5 minutes (not_yet_valid). Elle accepte exp il y a 10 secondes, dans la tolérance de 30 secondes. La vérification de l’émetteur refuse iss localhost:9999 (wrong_issuer), et celle des revendications refuse un sub manquant (missing_claim). La vérification du type de jeton refuse le jeton d’identité de Camille (wrong_token_type). Si cette vérification est désactivée, celle de l’audience le refuse (wrong_audience). La liste d’autorisation refuse alg none et alg HS256 (algorithm_not_allowed). La vérification de clé refuse un kid que le fournisseur n’a jamais publié (unknown_key). La vérification de signature refuse une autre clé avec le kid du fournisseur (bad_signature). La vérification du format refuse la chaîne abc.def (malformed). Pour le cours, l’auteur a signé de nouveau chaque jeton modifié, sauf celui dont le sub a été modifié. La clé du fournisseur ou une autre clé l’a signé, pour qu’une seule vérification à la fois le refuse.
Ce que cela nous apprend. Une modification change la partie qui la contient. La signature ne peut pas suivre, car seule la clé privée peut en créer une nouvelle. La vérification de signature détecte donc chaque modification, quel que soit son contenu.
Ce que prouve une signature, et ses limites
Section intitulée « Ce que prouve une signature, et ses limites »| Une signature valide prouve | Une signature valide ne prouve pas |
|---|---|
| Le fournisseur qui possède cette clé a créé le jeton. | Que le jeton est secret. Tout le monde peut le décoder. Un jeton ne doit donc jamais contenir un mot de passe ou d’autres données secrètes. |
| Aucun caractère de l’en-tête ou des revendications n’a changé. | Que le jeton est destiné à cette API. Un jeton pour billing-api a aussi une signature valide. |
| Que le jeton est encore valide. Un jeton expiré garde sa signature valide pour toujours. | |
Que l’expéditeur est la personne indiquée dans sub. Comme un billet de banque, un jeton fonctionne pour quiconque le détient : c’est un jeton au porteur. |
La colonne de droite explique pourquoi une signature valide est le début, pas la fin. Ce que signifie un jeton ajoute les vérifications de l’audience, du temps, de l’émetteur et du type de jeton. Le module 3 réduit le risque lié au porteur : les jetons restent sur le serveur, et le navigateur reçoit seulement un témoin (cookie).
Quand utiliser des jetons autonomes, et ce qu’ils coûtent
Section intitulée « Quand utiliser des jetons autonomes, et ce qu’ils coûtent »Un JWT est autonome : l’API le vérifie avec une clé publique, sans appeler le fournisseur à chaque requête. Cela rend la vérification rapide et évite de faire intervenir le fournisseur dans chaque requête. Les coûts découlent du même fait :
- Aucune fin anticipée. Le fournisseur ne peut pas révoquer un JWT avant
exp. Le jeton d’une personne dont le compte est désactivé fonctionne jusqu’à son expiration. Une courte durée de vie limite ce risque : 10 minutes ici. Le module 5 le mesure. - Taille. 732 caractères accompagnent chaque requête.
- Tout le monde peut le lire. Gardez les revendications courtes et sans danger : des identifiants, pas des renseignements personnels. Le jeton d’accès ici ne contient ni nom ni courriel.
Certains fournisseurs émettent plutôt des jetons opaques : des chaînes aléatoires que seul le fournisseur peut lire. L’API interroge alors le fournisseur sur chaque jeton, par un appel nommé introspection. Cela permet une fin anticipée, au prix d’un appel supplémentaire par requête. Ce cours utilise des JWT, comme la plupart des fournisseurs le font pour les API.
À vous de jouer : le jeton d’identité
Section intitulée « À vous de jouer : le jeton d’identité »Décodez le jeton d’identité de la même connexion et trouvez trois différences avec le jeton d’accès. Prédisez-les d’abord.
python -m scripts.token decode --user usr-sam --id-tokenAfficher la réponse
L’exécution de l’auteur, après une nouvelle connexion :
A JWT is three parts, separated by dots: 70, 298 and 342 characters.1. header (base64url JSON): {"alg": "RS256", "kid": "My9O_SxNpIg4Ff10", "typ": "JWT"}2. claims (base64url JSON): iss "http://localhost:8400" sub "usr-sam" aud "ticket-cli" exp 1791613209 (2026-10-10 06:20:09 UTC) iat 1791612609 (2026-10-10 06:10:09 UTC) auth_time 1791612609 (2026-10-10 06:10:09 UTC) name "Sam" email "sam@larkfield.example" email_verified true nonce "7jPwIjXDl9Xe3KTUl3bh3g"3. signature: R8x4xTemR8kFZVaD... (256 bytes)Decoding is not checking: anyone can read these parts. Only the signature, checked withthe provider's public key 'My9O_SxNpIg4Ff10', shows that the provider made them.typvautJWT, et nonat+jwt: l’en-tête indique que ce n’est pas un jeton d’accès.audvautticket-cli, l’application, et nonticket-api.- Il contient les champs
name(nom),email(courriel),auth_time(moment où Sam s’est connecté) etnonce. Il ne contient niscope(portée) niclient_id(identifiant du client).
Le même fournisseur et la même clé ont signé les deux jetons. Les deux signatures sont donc valides. La signature seule ne permet pas à l’API de savoir quel jeton elle a reçu. L’en-tête et les revendications le permettent.
Erreur courante
Faire confiance à un jeton seulement décodé
Symptôme (construit) : un script utilitaire détermine qui fait l’appel avec jwt.decode(token, options={"verify_signature": False}). La copie modifiée du jeton de Camille passe. Le script considère alors que l’appelant est usr-grace, le propriétaire de Larkfield.
Cause : le décodage lit seulement le base64url. Il ne vérifie rien. scripts/login.py et scripts/token.py décodent sans vérifier, volontairement, pour montrer un jeton. Les deux l’indiquent dans leur sortie : « decoded, not checked » (décodé, non vérifié).
Diagnostic : cherchez dans le code les endroits qui lisent un jeton sans le vérifier :
grep -rn "verify_signature\|get_unverified" --include='*.py' ticket_api scripts idpSelect-String -Path ticket_api\*.py, scripts\*.py, idp\*.py -Pattern "verify_signature|get_unverified"À cette étape, huit lignes correspondent dans votre projet. Sept sont dans scripts/, pour montrer les jetons. Une est dans idp/app.py : elle lit seulement l’en-tête pour choisir la clé. Après la prochaine leçon, une neuvième ligne se trouve dans ticket_api/auth.py. Elle aussi lit seulement l’en-tête, pour choisir la vérification. Toute autre correspondance qui décide de l’accès constitue le bogue.
Correction :
- Décidez de l’accès seulement avec un jeton vérifié par le validateur de jetons de l’API, construit dans la prochaine leçon.
- Lisez un en-tête non vérifié seulement pour choisir comment vérifier, jamais s’il faut faire confiance.
- Ajoutez un test qui envoie un jeton modifié et attend une réponse 401.
scripts.token casesen contient un :tampered(modifié).
Vérifiez votre compréhension
Pourquoi presque tous les JWT commencent-ils par les trois mêmes caractères, eyJ? Qu'est-ce que cela vous apprend sur la confidentialité du contenu d'un jeton?
Afficher la réponse
eyJ représente en base64url les deux caractères {" qui commencent chaque en-tête JSON. Base64url transforme seulement les octets en caractères sûrs, à raison de 4 caractères pour 3 octets. Ce n’est pas du chiffrement. Toute personne qui possède un jeton peut donc lire son en-tête et ses revendications. Ne mettez jamais de secret dans un jeton. Traitez le jeton lui-même comme un secret, car toute personne qui le possède peut l’utiliser.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
L'API possède la clé publique du fournisseur. L'API, ou un attaquant qui lit /jwks.json, peut-elle créer un jeton qu'elle accepte? Pourquoi?
Afficher la réponse
Non. La clé publique peut seulement vérifier les signatures. Pour créer une signature, il faut la clé privée, qui reste dans le dossier .idp/keys/ du fournisseur. Dans l’exemple simplifié, les nombres publics 3 233 et 17 vérifient la signature 588. Sans le nombre privé 2 753, personne ne peut calculer la signature d’une nouvelle empreinte. C’est pourquoi le fournisseur peut publier la liste des clés pour tout le monde.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Un jeton a une signature valide de votre fournisseur. Nommez trois raisons pour lesquelles l'API pourrait quand même devoir le refuser.
Afficher la réponse
Il peut être destiné à une autre API (aud n’est pas ticket-api). Il peut être expiré (exp est dans le passé) ou pas encore valide (nbf est dans le futur). Il peut s’agir d’un jeton d’identité, et non d’un jeton d’accès (typ vaut JWT, aud désigne l’application). Une signature valide ne prouve pas non plus que l’expéditeur est la personne désignée par sub. Toute personne qui possède le jeton peut l’envoyer.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Un JWT comporte trois parties en base64url : l’en-tête (
alg,kid,typ), les revendications et la signature. Base64url n’est pas du chiffrement :{"devienteyJ, et tout le monde peut lire chaque revendication. - Les revendications du jeton d’accès :
iss,sub,aud,exp,nbf,iat,jti,client_id,scope, avec les heures en secondes Unix. Aucune organisation, aucun rôle, aucun nom. - RS256 : le fournisseur signe avec sa clé privée; tout le monde peut vérifier avec la clé publique. Une revendication modifiée ne correspond plus à sa signature.
- Le fournisseur publie ses clés publiques sous forme de JWKS à
/jwks.json; le champkidde l’en-tête désigne la clé. Pendant une rotation des clés, la liste contient deux clés. - Une signature valide prouve qui a créé le jeton et que rien n’a changé. Elle ne prouve ni l’audience, ni l’heure, ni le type de jeton, ni l’identité de l’expéditeur.
- Ensuite : l’API vérifie tout cela, dans l’ordre, et indique quelle vérification a refusé un jeton.
Leçon suivante : Ce que signifie un jeton