Ce que signifie un jeton
- Leçon 7
- 55 min
- Module 2 : Comprendre la connexion et les jetons
Leçon 7 sur 19 · Authentification, autorisations et applications multilocatairesCe que signifie un jeton
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
Faites vérifier chaque jeton d’accès par l’API avant de lui faire confiance : algorithme, type, clé, signature, temps, émetteur et audience. Refusez un mauvais jeton avec une raison claire. Gardez les clés du fournisseur en cache pour que la vérification reste rapide.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les trois parties d’un JWT, les revendications (claims) et la signature, dans Lire un jeton : JWT, signatures et clés.
- Le jeton d’accès, le jeton d’identité et leurs audiences, dans Flux d’application.
- Les dépendances FastAPI et les réponses d’erreur, dans Créer et déployer votre première API.
Ce qu’il vous faut sur votre ordinateur
- Le projet après Lire un jeton : JWT, signatures et clés, avec
scripts/login.pyetscripts/token.py. - Le fournisseur qui tourne dans son propre terminal (
python -m idp). PostgreSQL et Azurite en marche.
Après cette leçon, votre projet correspond à l’instantané end-of-m02 et compte 181 tests. Chaque mauvais jeton de cette leçon est un cas construit : créé pour le cours avec la clé de votre fournisseur d’exercice, puis envoyé à votre propre API. Les tests ont été faits sur macOS (Apple silicon); les commandes Windows sont écrites, mais non testées. Les temps ont été mesurés sur un ordinateur Apple M4 (16 Go, macOS), avec peu d’autres programmes en marche. Vos résultats seront différents, mais la tendance restera la même.
Un billet à l’entrée du concert
Section intitulée « Un billet à l’entrée du concert »Un préposé se tient à l’entrée d’une salle de concert. Vous lui donnez un billet. Le préposé vérifie d’abord l’hologramme : est-ce un vrai billet? Puis les détails. Vient-il de la billetterie de cette salle? Est-il pour cette salle, et non pour le théâtre voisin? Est-il pour ce soir? Est-ce bien un billet, ou un reçu de vestiaire?
Un vrai hologramme sur un billet pour le théâtre voisin ne rend pas le billet valable. Chaque vérification bloque une erreur différente. Le préposé accorde aussi un peu de temps : une personne qui arrive deux minutes après la fermeture des portes entre quand même.
Le problème : une vérification qui s’arrête à la signature
Section intitulée « Le problème : une vérification qui s’arrête à la signature »Lire un jeton : JWT, signatures et clés se terminait par une liste : une signature valide ne prouve ni l’audience, ni le temps, ni le type du jeton. À quel point est-ce risqué de s’arrêter là? L’auteur a écrit une vérification d’API volontairement incorrecte qui teste seulement la signature. Elle utilisait la bonne clé et autorisait seulement RS256, sans rien vérifier d’autre. Puis elle a reçu les 15 jetons inventés de cette leçon :
accepted valid sub=usr-sam aud=ticket-api refused no_token accepted expired sub=usr-sam aud=ticket-api accepted expired_within_leeway sub=usr-sam aud=ticket-api accepted not_yet_valid sub=usr-sam aud=ticket-api accepted wrong_audience sub=usr-sam aud=billing-api accepted wrong_issuer sub=usr-sam aud=ticket-api accepted id_token sub=usr-sam aud=help-desk-web refused alg_none refused hs256_public_key refused unknown_key refused wrong_key_same_kid refused tampered accepted missing_sub sub=None aud=ticket-api refused not_a_jwt
A signature-only check accepted 8 of 15 tokens; the full check accepts 2 (valid, expired_within_leeway).Elle a accepté six jetons qu’elle doit refuser. Un a expiré il y a 5 minutes, et un autre n’est pas encore valide. Un a été créé pour billing-api, et un autre vient d’un autre émetteur. Les deux derniers sont le jeton d’identité de Sam et un jeton sans sub. Chacun a une vraie signature. Seules les revendications indiquent qu’ils ne conviennent pas.
L’idée : vérifier le sens, dans un ordre fixe
Section intitulée « L’idée : vérifier le sens, dans un ordre fixe »Un jeton signifie « ce fournisseur dit que cette personne peut utiliser cette API, de ce moment à ce moment ». L’API vérifie chaque partie de cette phrase. ticket_api/auth.py effectue ces vérifications dans l’ordre et s’arrête au premier échec :
- Est-ce bien un JWT? Trois parties en base64url. Sinon,
malformed(format incorrect). - L’algorithme figure dans la liste d’autorisation. L’API fixe la liste : RS256 seulement. Le champ
algdu jeton ne choisit jamais la vérification. Sinon,algorithm_not_allowed(algorithme non autorisé). - Un jeton d’accès : le champ
typde l’en-tête vautat+jwt. Un jeton d’identité indiqueJWT. Sinon,wrong_token_type(mauvais type de jeton). - Une clé publiée : le
kidfigure dans la liste de clés du fournisseur. Sinon,unknown_key(clé inconnue). - La signature correspond à cette clé. Sinon,
bad_signature(signature incorrecte). - Les revendications obligatoires sont présentes :
exp,iat,iss,audetsub. Sinon,missing_claim(revendication manquante). - Le temps : le moment actuel se situe entre
nbfetexp, avec 30 secondes de tolérance. Sinon,not_yet_valid(pas encore valide) ouexpired(expiré). - L’émetteur est notre fournisseur. Sinon,
wrong_issuer(mauvais émetteur). - L’audience est
ticket-api. Sinon,wrong_audience(mauvaise audience).
Pourquoi vérifier l’algorithme avant tout le reste? Parce qu’un attaquant écrit l’en-tête. Avec alg: none, une bibliothèque mal conçue saute la vérification de signature. Avec alg: HS256, elle utilise la clé publique comme secret partagé, alors que tout le monde peut lire cette clé. La deuxième attaque s’appelle la confusion d’algorithmes. Une liste d’autorisation fixe bloque les deux avant le début de la vérification de signature.
Pourquoi la tolérance? Deux ordinateurs ont rarement exactement la même heure. Si l’horloge du fournisseur avance de quelques secondes, un nouveau jeton peut sembler « pas encore valide » à l’API. Une petite tolérance accepte cet écart. Voici la règle avec toutes les valeurs visibles. L’horloge de l’API indique 1 000; la tolérance est de 30 secondes :
| Jeton | Règle | Résultat |
|---|---|---|
exp = 990 (expiré il y a 10 secondes) |
refuser si exp ≤ 1 000 − 30 = 970 |
990 > 970 : accepté |
exp = 700 (expiré il y a 5 minutes) |
refuser si exp ≤ 970 |
700 ≤ 970 : refusé, expired |
nbf = 1 300 (valide dans 5 minutes) |
refuser si nbf > 1 000 + 30 = 1 030 |
1 300 > 1 030 : refusé, not_yet_valid |
Un jeton est donc accepté jusqu’à 30 secondes après son exp. C’est le coût de la tolérance. Gardez-la petite : des secondes, pas des minutes.
Chaque refus est un 401 avec le code invalid_token (jeton invalide). L’API nomme aussi la vérification dans l’en-tête X-Token-Check. Elle envoie l’en-tête standard WWW-Authenticate: Bearer pour que le client sache qu’il lui faut un nouveau jeton. Elle n’écrit jamais le jeton lui-même dans un journal.
Essayez les vérifications sur le vrai jeton de Camille. Cette fois, commencez par son jeton d’identité :
Explorer
Quelle vérification refuse quel jeton?
Prédisez d’abord : Keycloak et Microsoft Entra ID écrivent aussi typ JWT dans leurs jetons d'accès. L'API doit donc désactiver sa vérification du type de jeton pour eux. Le jeton d'identité de Camille arrive à une telle API. Est-il accepté? Sinon, quelle vérification le refuse?
Le vrai jeton d’identité de Camille, de la même connexion, envoyé comme jeton d’accès. Refusé par la vérification 3 : wrong_token_type. 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
eyJhbGciOiJSUzI1NiIsImtpZCI6IjdIeGNCdGs0ZGpkcnNQekIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg0MDAiLCJzdWIiOiJ1c3ItY2FtaWxsZSIsImF1ZCI6InRpY2tldC1jbGkiLCJleHAiOjE3OTE2MTE3NzAsImlhdCI6MTc5MTYxMTE3MCwiYXV0aF90aW1lIjoxNzkxNjExMTcwLCJuYW1lIjoiQ2FtaWxsZSIsImVtYWlsIjoiY2FtaWxsZUBsYXJrZmllbGQuZXhhbXBsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJub25jZSI6IkZNVlhJLUVsNWtWZW1EMzRTZW5makEifQ.QR15TTMFwIa0G4n1kNk_ftk7DU_EVCIEPyKc3bLSE3Igt8ZhJjryOGBUCQ8ujZn2w38Nn6Ee9Fu2vIlG8CdFqj8UOqlst4DUDpuGhPLeNVfueQ2rz0G4h1SKApmpaTL_KROHSU7kaYiUNV7jPCQuzOmlUt17XiqTFxCU9cYQhfSMgZ1tX5IgTzMZLEDpFYRcvauNHa04f7fSzEq-Qa38BNKDyILRv8D4M9LyW4rHXEtocRn-HjHKTsizdb-2s_u_grnDRIMuEBAeQqpAIbSv1W3Lj6lqYUclM3mInLSOWnFhyYpOrKmf93mf7qRrETZRKZOD6J1U5GKS1-u4bZ5Z3A
alg | "RS256" |
|---|---|
kid | "7HxcBtk4djdrsPzB" |
typ | "JWT" |
iss | "http://localhost:8400" |
|---|---|
sub | "usr-camille" |
aud | "ticket-cli" |
exp | 17916117702026-10-10 05:56:10 UTC |
iat | 17916111702026-10-10 05:46:10 UTC |
auth_time | 17916111702026-10-10 05:46:10 UTC |
name | "Camille" |
email | "camille@larkfield.example" |
email_verified | true |
nonce | "FMVXI-El5kVemD34SenfjA" |
3. Signature : La vraie signature du jeton d’identité par le 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)échec : typ vaut "JWT" →
wrong_token_type - 4. Identifiant de clé publié par le fournisseurpas atteinte : pas atteinte
- 5. La signature correspond à cette clé publiquepas atteinte : pas atteinte
- 6. Revendications obligatoires présentespas atteinte : pas atteinte
- 7. Entre nbf et exp (tolérance de 30 s)pas atteinte : pas atteinte
- 8. L’émetteur est notre fournisseurpas atteinte : pas atteinte
- 9. L’audience est ticket-apipas atteinte : pas atteinte
GET /v1/me → 401 invalid_token
X-Token-Check: wrong_token_type
“This is not an access token (an ID token is for the application, not the API).”
Description textuelle de l’explorateur
L’explorateur commence avec le vrai jeton d’identité de Camille, issu de la même connexion, envoyé comme jeton d’accès. Quand la vérification du type est activée, la vérification 3 le refuse : son typ vaut JWT, et non at+jwt. L’API répond 401 avec X-Token-Check : wrong_token_type. Le message dit : « Ce n’est pas un jeton d’accès (un jeton d’identité est destiné à l’application, pas à l’API). »
Quand la vérification du type est désactivée, comme pour Keycloak ou Entra ID, les vérifications 4 à 8 réussissent. La clé est publiée, la signature est vraie et les revendications sont présentes. Le temps se situe dans la fenêtre, et l’émetteur est le fournisseur d’exercice. La vérification 9 le refuse : son audience est ticket-cli, l’application. L’API répond donc wrong_audience, « Le jeton n’est pas destiné à ticket-api. »
Les autres boutons changent une seule chose dans le jeton d’accès de Camille. Exactement une vérification refuse chaque changement, dans l’ordre de la liste. La vérification du format refuse abc.def (malformed). La liste d’autorisation refuse alg none et HS256 (algorithm_not_allowed). La vérification de clé refuse un kid non publié (unknown_key). La vérification de signature refuse une autre clé et un sub modifié (bad_signature). La vérification des revendications refuse l’absence de sub (missing_claim). La vérification du temps refuse nbf dans 5 minutes (not_yet_valid) et exp il y a 5 minutes (expired). La vérification de l’émetteur refuse iss localhost:9999 (wrong_issuer), et celle de l’audience refuse aud billing-api (wrong_audience). L’API accepte exp il y a 10 secondes, dans la tolérance de 30 secondes.
Ce que cela nous apprend. La vérification de l’audience est la plus importante. Elle refuse un jeton d’identité même quand le fournisseur n’écrit aucun typ utile. Elle refuse aussi chaque jeton créé pour une autre API. La vérification de typ est un ajout peu coûteux qui donne un message plus clair quand le fournisseur la permet.
Larkfield : la vérification du jeton dans l’API
Section intitulée « Larkfield : la vérification du jeton dans l’API »La vérification d’Amira se trouve dans ticket_api/auth.py. Voici son cœur : les trois vérifications d’en-tête, la clé, puis un appel à PyJWT qui vérifie ensemble signature et revendications.
header = jwt.get_unverified_header(token) # only to choose the checkif header.get("alg") not in ALGORITHMS: # ["RS256"], fixed in the code raise TokenRejected("algorithm_not_allowed", ...)if self.check_type and header.get("typ") not in ACCESS_TOKEN_TYPES: raise TokenRejected("wrong_token_type", ...)key = self.provider.key(header.get("kid", "")) # from the cached JWKSif key is None: raise TokenRejected("unknown_key", ...)claims = jwt.decode( token, key.key, algorithms=ALGORITHMS, # the allow-list again audience=self.audience, # OIDC_AUDIENCE, ticket-api issuer=self.provider.issuer, # OIDC_ISSUER leeway=self.leeway, # TOKEN_LEEWAY_SECONDS, 30 options={"require": ["exp", "iat", "iss", "aud", "sub"]},)Chaque erreur PyJWT devient un refus nommé; par exemple, ExpiredSignatureError devient expired. Un jeton vérifié devient une Identity : le sub, le client_id, les portées et l’expiration. Un nouveau point de terminaison, GET /v1/me, montre la personne qui appelle telle que l’API la voit. L’identité vient du jeton. Les adhésions viennent des tables de l’API, car le jeton n’en contient aucune.
-
Copiez les fichiers de cette leçon depuis l’instantané
end-of-m02. Ils contiennent la vérification du jeton, le nouveau point de terminaison, ses paramètres et réponses d’erreur, ainsi que leurs tests :Fenêtre de terminal S=../nextia-learning-labs/C23/snapshots/end-of-m02for f in ticket_api/auth.py ticket_api/me.py ticket_api/security.py ticket_api/config.py \ticket_api/errors.py ticket_api/main.py ticket_api/models.py ticket_api/repository.py \contract/openapi.json pyproject.toml compose.yaml .env.example \tests/conftest.py tests/test_identity_provider.py tests/test_tokens.py tests/test_me.py; docp "$S/$f" "$f"doneFenêtre de terminal $S = "..\nextia-learning-labs\C23\snapshots\end-of-m02"$files = 'ticket_api\auth.py', 'ticket_api\me.py', 'ticket_api\security.py', 'ticket_api\config.py','ticket_api\errors.py', 'ticket_api\main.py', 'ticket_api\models.py', 'ticket_api\repository.py','contract\openapi.json', 'pyproject.toml', 'compose.yaml', '.env.example','tests\conftest.py', 'tests\test_identity_provider.py', 'tests\test_tokens.py', 'tests\test_me.py'foreach ($f in $files) { Copy-Item "$S\$f" $f }La version de l’API est maintenant 1.4.0, et son contrat contient le nouveau point de terminaison.
-
Ouvrez
.envet ajoutez les deux nouvelles lignes de.env.exampleà la fin :.env (two new lines at the end) OIDC_ISSUER=http://localhost:8400OIDC_AUDIENCE=ticket-apiOIDC_ISSUERest le nom et l’adresse du fournisseur. S’il est vide, la connexion est désactivée. Chaque route qui exige un jeton répond503 sign_in_unavailable(connexion indisponible). -
Exécutez les tests. Les tests du fournisseur le font tourner dans le processus de test; ils n’ont donc besoin d’aucun serveur :
Fenêtre de terminal python -m pytestLe résultat attendu est
181 passed, 1 deselected(181 tests réussis, 1 non sélectionné). Votre projet correspond maintenant à l’instantanéend-of-m02. -
Démarrez l’API dans un autre terminal :
fastapi dev. Vous avez maintenant trois terminaux : le fournisseur, l’API et vos commandes. -
Appelez
/v1/mesans jeton, puis avec le jeton d’accès de Sam :Fenêtre de terminal curl -i http://127.0.0.1:8000/v1/mecurl -H "Authorization: Bearer $(python -m scripts.login --user usr-sam --print-access-token)" http://127.0.0.1:8000/v1/meFenêtre de terminal curl.exe -i http://127.0.0.1:8000/v1/me$t = python -m scripts.login --user usr-sam --print-access-tokencurl.exe -H "Authorization: Bearer $t" http://127.0.0.1:8000/v1/me -
Faites la même chose pour
usr-camilleetusr-tomas, puis comparez leursmemberships(adhésions).
Afficher le résultat attendu
Sans jeton, l’API en demande un (les lignes de date, de serveur et de longueur sont omises) :
HTTP/1.1 401 Unauthorizedcontent-type: application/jsonwww-authenticate: Bearer realm="ticket-api"x-request-id: c555e193389d
{"error":{"code":"not_signed_in","message":"Send an access token: Authorization: Bearer <token>.","request_id":"c555e193389d","fields":[]}}Avec le jeton de Sam (ici mis en forme avec python -m json.tool) :
{ "user_id": "usr-sam", "kind": "user", "name": "Sam", "platform_role": null, "client_id": "ticket-cli", "scopes": ["members:manage", "tickets:read", "tickets:write"], "token_expires_at": "2026-10-10T06:21:19Z", "memberships": [ {"tenant_id": "larkfield", "tenant_name": "Larkfield", "role": "staff"} ]}Camille a deux adhésions : bramble comme read_only et larkfield comme staff. Tomás a "memberships": [] : il peut se connecter, mais il n’appartient à aucune boutique. Votre token_expires_at se situe 10 minutes après votre connexion.
Envoyez maintenant tous les jetons inventés. scripts.token cases crée chacun avec la clé de votre fournisseur et l’envoie au code de l’API dans le même processus. Aucun serveur n’est donc nécessaire :
python -m scripts.token casesLe journal de l’API affiche d’abord une ligne par requête. Le tableau vient à la fin :
Made-up tokens sent to GET /v1/me (constructed for the course):
valid 200 accepted Sam's access token, as the provider issued it no_token 401 refused by: not_signed_in no Authorization header at all expired 401 refused by: expired expired 5 minutes ago expired_within_leeway 200 accepted expired 10 seconds ago (inside the 30 s leeway) not_yet_valid 401 refused by: not_yet_valid valid only from 5 minutes from now (nbf) wrong_audience 401 refused by: wrong_audience made for another API (aud billing-api) wrong_issuer 401 refused by: wrong_issuer from another issuer id_token 401 refused by: wrong_token_type Sam's ID token sent as an access token alg_none 401 refused by: algorithm_not_allowed unsigned (alg none) hs256_public_key 401 refused by: algorithm_not_allowed HS256, with the provider's PUBLIC key as the secret unknown_key 401 refused by: unknown_key signed with a key the provider never published wrong_key_same_kid 401 refused by: bad_signature signed with another key, using the provider's kid tampered 401 refused by: bad_signature sub changed to usr-grace after signing (signature kept) missing_sub 401 refused by: missing_claim no sub claim not_a_jwt 401 refused by: malformed a random stringQuinze jetons, et chacun est refusé par la vérification que son erreur désigne. Le test test_every_made_up_token_gets_the_expected_answer garantit ce comportement.
Erreur courante
Tomás : « le jeton d'identité convient pour appeler l'API »
Symptôme : Tomás crée une petite page de rapport. Elle se connecte et prend le id_token dans la réponse du fournisseur, parce qu’« il contient le nom de l’utilisateur ». Elle l’envoie ensuite à l’API. Chaque appel reçoit 401 :
HTTP/1.1 401 Unauthorizedwww-authenticate: Bearer realm="ticket-api", error="invalid_token", error_description="This is not an access token (an ID token is for the application, not the API)."x-token-check: wrong_token_typeCause : le jeton d’identité est une note destinée à l’application. Son aud est l’application (ticket-cli ou help-desk-web), et son en-tête indique typ: JWT. Il n’a aucune portée. Il ne dit donc rien sur ce que l’application peut faire dans l’API.
Diagnostic : lisez X-Token-Check. Puis décodez ce que la page envoie avec python -m scripts.token decode <token>. Un jeton d’identité contient name, email et nonce, mais aucun scope.
Correction :
- Envoyez le
access_tokende la même réponse. Lisez le nom de la personne dans le jeton d’identité ou dans/v1/me. - Gardez la vérification de l’audience, même quand vous désactivez celle de
typ. Avec Keycloak ou Entra ID, dont les jetons d’accès indiquent aussityp: JWT, le paramètreACCESS_TOKEN_TYPES=JWT(module 3) la désactive. La vérification de l’audience refuse alors le jeton d’identité :wrong_audience. Le testtest_an_id_token_is_also_for_another_audiencele prouve.
Clés en cache : rapide, avec un temps d’attente
Section intitulée « Clés en cache : rapide, avec un temps d’attente »L’API a besoin des clés publiques du fournisseur pour la vérification 4. Récupérer /jwks.json à chaque requête est lent et fait intervenir le fournisseur dans chaque requête. L’API garde donc les clés en mémoire pendant JWKS_CACHE_SECONDS, soit 600 secondes par défaut. Le cours a mesuré les deux méthodes avec le même jeton, en 5 passes : 200 vérifications par passe avec le cache, 100 sans.
Description textuelle du graphique
Un diagramme à colonnes en millisecondes. Avec les clés en cache : 0,065 ms par vérification, trop peu pour être visible à cette échelle. Avec les clés récupérées par HTTP à chaque vérification : 23,4 ms. L’exécution avec cache a récupéré les clés une fois; l’autre les a récupérées à chaque vérification, 525 fois avec la phase de préparation.
Avec les clés en cache, une vérification a pris 0,065 ms. Les récupérer chaque fois a pris 23,4 ms : environ 360 fois plus lent. Chaque récupération ouvre une nouvelle connexion à localhost:8400, et le fournisseur lit son fichier de clés. Avec un fournisseur sur Internet, une récupération prend encore plus de temps. L’exécution avec cache a récupéré les clés une seule fois pour toutes ses vérifications.
Un cache a un coût : les nouvelles clés arrivent tard. Quand un jeton nomme un kid absent du cache, l’API récupère de nouveau les clés immédiatement. Mais elle le fait au plus une fois toutes les JWKS_MIN_REFRESH_SECONDS, soit 10 secondes. Sans cette limite, un flot de jetons aux identifiants de clé inventés ferait aussi submerger le fournisseur par l’API. Le cours a mesuré ce que cela signifie lors d’une rotation des clés :
Description textuelle du diagramme
Dans la séquence A, la nouvelle clé a signé immédiatement, juste après que l’API avait récupéré les clés. Pendant 10,1 secondes, chaque requête avec un nouveau jeton a reçu 401 unknown_key : 39 refus lors de la mesure. Dans la séquence B, la clé a été publiée d’abord et n’a signé que 11 secondes plus tard. La dernière récupération de l’API datait alors de plus de 10 secondes. Le kid inconnu a donc déclenché une récupération, et le jeton a été accepté immédiatement.
Faites donc la rotation en deux étapes : python -m idp rotate-key, attendez, puis python -m idp activate-key. Un vrai fournisseur fait la même chose. Avec une longue durée de cache, l’attente compte davantage. Publiez la nouvelle clé au moins une durée de cache (ici 600 secondes) avant qu’elle signe. Chaque API l’aura alors déjà.
Le cache a un deuxième coût. Une clé retirée par le fournisseur reste dans le cache d’une API jusqu’à la prochaine récupération, jusqu’à 600 secondes. Si le fournisseur est en panne, une API en marche continue de vérifier les jetons avec ses clés en cache. Une API qui vient de démarrer ne le peut pas. Dans l’exécution de l’auteur, elle a répondu 503 identity_provider_unavailable (fournisseur d’identité indisponible), « Le fournisseur d’identité ne répond pas. Réessayez plus tard. »
Quand utiliser chaque vérification et ce qu’elle coûte
Section intitulée « Quand utiliser chaque vérification et ce qu’elle coûte »Utilisez toutes les vérifications à chaque requête. Ensemble, elles coûtent moins d’un dixième de milliseconde avec des clés en cache. Changez les paramètres seulement pour une raison précise :
- Tolérance (
TOKEN_LEEWAY_SECONDS, 30) : l’augmenter masque les problèmes d’horloge et garde les jetons expirés valides plus longtemps. Corrigez plutôt les horloges. - Cache de clés (
JWKS_CACHE_SECONDS, 600) : une durée plus courte détecte plus tôt les clés nouvelles et retirées, et appelle le fournisseur plus souvent. - Type de jeton (
ACCESS_TOKEN_TYPES) : gardez la vérification activée pour un fournisseur qui écritat+jwt. Désactivez-la pour Keycloak et Entra ID, et fiez-vous à l’audience. - Audience (
OIDC_AUDIENCE) : ne désactivez jamais cette vérification. Elle bloque les jetons destinés à d’autres API et les jetons d’identité.
Un jeton vérifié répond seulement à la première question : qui appelle? Tomás a un jeton parfait et aucune adhésion. Les réponses aux questions suivantes, ce que Sam peut faire et dans quelle boutique, viennent des tables de l’API au module 4.
À vous de jouer : réduisez la tolérance, changez l’audience
Section intitulée « À vous de jouer : réduisez la tolérance, changez l’audience »Pour terminer ce module, identifiez la bonne audience et refusez un jeton expiré ou destiné à une autre audience. Changez un seul paramètre à la fois, pour une seule commande.
-
Faites d’abord une prédiction : sans tolérance, quelle ligne de
token cases(cas de jetons) change?Fenêtre de terminal TOKEN_LEEWAY_SECONDS=0 python -m scripts.token cases 2>/dev/nullFenêtre de terminal $env:TOKEN_LEEWAY_SECONDS = "0"; python -m scripts.token cases 2>$null; Remove-Item Env:TOKEN_LEEWAY_SECONDS -
Faites une autre prédiction : si l’API croyait être
billing-api, quels jetons accepterait-elle?Fenêtre de terminal OIDC_AUDIENCE=billing-api python -m scripts.token cases 2>/dev/nullFenêtre de terminal $env:OIDC_AUDIENCE = "billing-api"; python -m scripts.token cases 2>$null; Remove-Item Env:OIDC_AUDIENCE
Afficher la réponse
Sans tolérance, le jeton expiré depuis 10 secondes est refusé. Sa description indique encore « dans la tolérance de 30 secondes », car ce texte est fixe :
expired_within_leeway 401 refused by: expired expired 10 seconds ago (inside the 30 s leeway)Avec l’audience billing-api, le vrai jeton de Sam et le jeton expiré depuis 10 secondes sont refusés avec wrong_audience (mauvaise audience). Le jeton créé pour billing-api est accepté :
valid 401 refused by: wrong_audience Sam's access token, as the provider issued it expired_within_leeway 401 refused by: wrong_audience expired 10 seconds ago (inside the 30 s leeway) wrong_audience 200 accepted made for another API (aud billing-api)L’audience est le nom de l’API elle-même. Un jeton est valide seulement pour l’API qu’il nomme. La bonne audience pour chaque jeton accepté par ticket-api est ticket-api. Les 12 autres lignes ne changent pas.
Vérifiez votre compréhension
Trois jetons de votre fournisseur ont tous une signature valide et respectent leur période de validité. A a aud ticket-api et typ at+jwt; B a aud help-desk-web et typ JWT; C a aud billing-api et typ at+jwt. Lequel ticket-api accepte-t-il, et quelle vérification refuse chacun des autres?
Afficher la réponse
Seulement A. B est un jeton d’identité : la vérification 3 le refuse avec wrong_token_type (mauvais type de jeton). Si la vérification du type de jeton est désactivée, la vérification 9 le refuse avec wrong_audience (mauvaise audience). C est un jeton d’accès destiné à une autre API : la vérification 9 le refuse avec wrong_audience. L’audience doit être le nom de l’API elle-même, ticket-api.
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'horloge de l'API indique 1 000, et la tolérance est de 30 secondes. Un jeton avec exp 975 est-il accepté? Et un jeton avec exp 960? Montrez la règle.
Afficher la réponse
L’API refuse un jeton si son exp est inférieur ou égal à l’heure actuelle moins la tolérance : 970 ou moins. Un exp de 975 dépasse 970, donc le jeton est accepté. Il a expiré il y a 25 secondes, dans la tolérance. Un exp de 960 est inférieur à 970, donc le jeton est refusé avec expired (expiré). Avec TOKEN_LEEWAY_SECONDS=0, les deux sont refusés.
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
Pourquoi l'API prend-elle sa liste d'algorithmes dans son propre code (RS256 seulement), jamais dans le champ alg du jeton?
Afficher la réponse
Parce que l’expéditeur écrit l’en-tête. Avec alg none, une vérification mal conçue saute la signature. Avec alg HS256, elle utilise la clé publique comme secret HMAC. Comme tout le monde peut lire cette clé, tout le monde peut signer. Une liste d’autorisation fixe refuse les deux avant la vérification de signature : algorithm_not_allowed (algorithme non autorisé). Les cas alg_none et hs256_public_key le montrent.
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
- Une vérification limitée à la signature acceptait 8 des 15 jetons inventés; la vérification complète en accepte 2. La signature est le début, pas la fin.
ticket_api/auth.pyvérifie dans l’ordre : JWT, liste d’autorisation d’algorithmes (RS256),typat+jwt,kidpublié, signature, revendications requises, temps avec 30 secondes de tolérance, émetteur, audience. Chaque refus renvoie401 invalid_token(jeton invalide), avec la vérification dansX-Token-Check.- La vérification de l’audience est essentielle : elle refuse les jetons destinés à d’autres API et les jetons d’identité. Elle le fait aussi quand le fournisseur écrit
typ: JWT(Keycloak, Entra ID). GET /v1/memontre qui appelle : l’identité vient du jeton, les adhésions viennent des tables de l’API. Le projet compte 181 tests et correspond àend-of-m02.- Clés en cache : 0,065 ms par vérification au lieu de 23,4 ms. Une nouvelle clé utilisée immédiatement pour signer a été refusée pendant 10,1 secondes; publiez les clés avant de les utiliser pour signer.
- Ensuite : gardez les jetons hors du navigateur, avec un témoin (cookie) de session.
Leçon suivante : Choix de session dans le navigateur