Résumé

  • Avec DPoP, connaître la chaîne du jeton ne suffit plus : celui qui la présente doit aussi signer une preuve avec la clé privée correspondant à l’empreinte publique attachée au jeton.
  • Cette preuve vise une méthode, un URI sans requête ni fragment, un instant, un identifiant de preuve, le jeton exact et, parfois, un nonce du serveur. Elle ne couvre pas tout le message et n’accorde aucun droit par elle-même.
  • Le serveur de ressource doit encore vérifier émetteur, audience, durée, statut et portée du jeton, coordonner l’anti-rejeu, reconstruire le bon URI externe et décider si le sujet peut produire cet effet sur cette ressource maintenant.

Deux refus qui ne racontent pas la même histoire

Une équipe de sécurité découvre un jeton d’accès dans la sortie d’un outil de diagnostic. Elle reproduit l’appel depuis une machine d’analyse. Le serveur refuse. Le jeton est de type DPoP et porte une liaison à l’empreinte d’une clé publique ; la machine d’analyse ne possède pas la clé privée. Une preuve signée avec une autre clé échoue au rapprochement. Rejouer une preuve capturée échoue sur la méthode, l’URI, l’âge, le nonce ou l’identifiant déjà consommé.

Ce premier refus montre que la contrainte d’émetteur fonctionne. La valeur volée n’est plus un bearer credential complet.

Le second refus se produit depuis l’application légitime. Elle dispose de la clé, fabrique une nouvelle preuve, associe le bon jeton et appelle la bonne route. Signature, htm, htu, ath, heure et unicité passent. Pourtant l’opération de suppression est rejetée : le jeton n’autorise que la lecture, son audience désigne un autre service, le compte a été suspendu ou l’objet est déjà irrévocable.

Une console qui classe ces deux refus sous « DPoP failed » perd l’essentiel. Dans le premier, la preuve de possession manque. Dans le second, la preuve est correcte et l’autorisation fait son travail. La frontière entre ces résultats est précisément la frontière d’autorité.

Le problème bearer que DPoP réduit

La RFC 6750 définit le bearer token par sa propriété la plus risquée : toute partie qui détient la valeur peut l’utiliser dans sa zone d’acceptation, sans prouver la possession d’un secret cryptographique. TLS, la protection du stockage, des durées courtes et une audience étroite limitent l’exposition ; ils ne changent pas la nature du jeton une fois copié.

La RFC 9449 ajoute une preuve au niveau applicatif. Le client choisit une paire de clés asymétriques. Au token endpoint, il présente un JWT DPoP signé. Le serveur d’autorisation peut lier le jeton délivré à l’empreinte de la clé publique. Au serveur de ressource, chaque appel apporte le jeton et une preuve nouvelle. Le récepteur vérifie que la clé de la preuve est celle qui accompagne le jeton.

La RFC 9700 place cette contrainte parmi les défenses recommandées contre l’usage de jetons dérobés. Elle recommande séparément la restriction d’audience et le moindre privilège. Cette séparation est décisive : lier le porteur à une clé ne corrige ni un jeton trop puissant ni un serveur qui accepte l’audience d’un voisin.

L’empreinte JWK ne nomme pas une personne. Elle ne prouve pas l’enregistrement d’un client, l’intégrité d’un appareil ou le consentement du resource owner. Elle est un identifiant déterministe de matériau public. Sa précision cryptographique ne lui donne pas une fonction politique ou métier qu’elle ne possède pas.

Une preuve DPoP n’est pas un JWT quelconque

L’en-tête protégé contient typ: dpop+jwt, un algorithme asymétrique admissible et le JWK public. Le serveur doit imposer le profil : refuser none, les MAC symétriques, les algorithmes hors politique, les clés privées embarquées et les types ambigus. La RFC 8725 explique pourquoi un résultat vert fourni par une bibliothèque JWT ne suffit pas : type, algorithme, issuer, audience et règles de validation appartiennent à l’application.

La charge utile de base comporte jti, htm, htu et iat. jti identifie la preuve avec une valeur assez imprévisible pour rendre une collision négligeable. htm reprend la méthode HTTP. htu reprend l’URI cible, mais sans query ni fragment. iat date la création.

Lorsque le jeton d’accès est présenté, ath contient le SHA-256 de sa valeur exacte. Le serveur recalcule ce hash. Une preuve préparée pour un jeton ne peut donc pas être accolée à un second jeton, même si les deux ont été liés à la même clé.

Si le serveur a envoyé DPoP-Nonce, la preuve suivante doit reprendre le nonce récent. Un nonce imprévisible réduit l’utilité de preuves pré-signées puis exfiltrées : il demande une réponse à une valeur que le serveur vient de choisir.

Le contrôle complet reste une suite d’actes. Il faut garantir un seul champ DPoP, parser un JWT bien formé, vérifier tous les claims requis, contraindre l’algorithme, valider la signature, comparer méthode et URI, appliquer la fenêtre temporelle, recalculer ath, rapprocher l’empreinte du jeton et faire respecter le nonce. Les noms de claims ne sont pas des agents autonomes.

La chaîne des empreintes

Trois liaisons voisines répondent à trois substitutions différentes.

La première attache le jeton à la clé. Le serveur d’autorisation conserve l'empreinte, souvent dans cnf.jkt. Le serveur de ressource doit comparer cette valeur à l’empreinte de la clé publique qui vérifie la preuve courante.

La deuxième attache la preuve au jeton. ath empêche qu’une signature capturée avec le jeton A soit réutilisée avec le jeton B. Elle ne dit rien de la validité, de la portée ou de l’audience de A.

La troisième peut commencer avant la délivrance. dpop_jkt lie le code d’autorisation à la clé attendue. Sans cette liaison, un attaquant qui récupère un code et les éléments nécessaires à son échange pourrait tenter de le présenter avec sa propre clé, puis recevoir un jeton correctement lié à la mauvaise partie. Cette protection ne remplace ni PKCE ni l’authentification du client ; elle ferme une autre étape.

Dire « PoP activé » sans préciser laquelle de ces liaisons est contrôlée rend l’audit presque inutile.

L’URI prouvé n’est pas toute la commande

La comparaison de htm empêche de transformer une preuve pour GET en preuve pour DELETE. Celle de htu empêche son transport vers une autre cible. Mais la cible DPoP ne contient ni query ni fragment. La spécification ne signe pas non plus, par défaut, le body ou les autres champs.

Ainsi, deux appels POST /virement?compte=A et POST /virement?compte=B peuvent partager le même htu. Un montant ou un bénéficiaire placé dans un corps JSON n’entre pas davantage dans la preuve de base. L’application doit relier ces paramètres au droit qu’elle applique. Si l’effet exige une autorisation transactionnelle, un objet de consentement, un identifiant d’idempotence ou une signature de message plus large, DPoP ne les fabrique pas.

La RFC 9110 fournit les sémantiques HTTP ; le service reste propriétaire de la sémantique métier. Cette modestie est saine. Une couche commune mince est vérifiable. Elle devient dangereuse lorsque l’organisation l’interprète comme une permission générale.

Les proxys ajoutent un problème de point de vue. Le navigateur signe l’URI externe. Une passerelle peut terminer TLS, réécrire le chemin et transmettre un host interne. Si le serveur final compare htu à sa propre vue sans règle de provenance, il rejettera les bons appels ou sera tenté d’accepter une normalisation trop large. Il faut nommer l’autorité qui reconstruit l’URI public et borner la chaîne de forwarding admise.

Rejeu : l’état distribué derrière jti

L’unicité déclarée de jti ne consomme rien. Le serveur doit conserver un état des preuves acceptées pendant la fenêtre utile et effectuer test puis écriture de manière atomique.

Dans un service multirégion, deux copies de la même preuve peuvent atteindre deux réplicas avant synchronisation. Chaque nœud voit alors une valeur neuve. Une courte fenêtre ne supprime pas la course ; elle en limite la durée. Un nonce non plus n’est pas magique : il faut savoir qui l’a émis, pour quelle portée, combien de temps il vit et si plusieurs usages sont permis.

Les nonces du serveur d’autorisation et du serveur de ressource ne sont pas interchangeables. Un client multiserveur doit les indexer par émetteur. Sinon un champ destiné à renforcer la fraîcheur devient une source d’erreurs croisées.

Enfin, refuser deux usages du même JWT de preuve ne garantit pas une exécution métier unique. Le client peut fabriquer deux preuves valides pour deux retries. Il faut une règle d’idempotence ou une transaction capable de dire si l’acte a déjà été commis.

La clé peut rester en place et être utilisée par le mauvais code

Dans un navigateur, une clé non extractible rend l’exfiltration plus difficile. La RFC 10017 montre cependant la limite : du JavaScript malveillant exécuté dans l’origine de l’application dispose des pouvoirs de cette origine. Il peut parfois demander une signature, faire appeler l’API ou lancer un nouveau flow sans emporter la clé hors du navigateur.

DPoP transforme donc l’attaque. Il réduit l’usage hors contexte d’un jeton copié. Il ne garantit pas que le contexte autorisé reste honnête. Si l’attaquant obtient le jeton et le matériau privé, ou un accès durable à l’interface de signature, la contrainte s’effondre.

La bonne question d’audit n’est pas seulement « la clé est-elle exportable ? ». C’est « qui peut lui demander de signer, pour quelles routes, avec quelles limites, et quelle preuve d’interaction ou de transaction le serveur exige-t-il encore ? »

Prouver la chaîne en fonctionnement

Les registres IANA donnent des noms communs à DPoP, DPoP-Nonce, aux types et aux paramètres OAuth. Ils ne garantissent ni une comparaison d’URI correcte ni un replay store cohérent.

Une trace utile conserve, sans secret, le hash de la preuve, l’empreinte publique, le type du jeton, les résultats ath et cnf.jkt, l’URI externe et l’URI interne normalisés, l’âge, l’émetteur du nonce, le résultat atomique anti-rejeu, issuer, audience, scopes, version de politique et identifiant de commit. Elle distingue chaque refus.

Le test doit traverser les composants : jeton volé sans clé ; clé juste avec mauvais jeton ; preuve trop vieille ; jti simultané dans deux régions ; mauvaise méthode ; réécriture d’URI ; nonce étranger ; audience voisine ; scope insuffisant ; compte suspendu ; opération déjà accomplie. La preuve d’autorité n’est pas qu’un client sait signer. C’est que tout le système sait expliquer pourquoi il a exécuté ou refusé.