Valider les JWT provenant de plusieurs issuers

Emmanuel Gautier Emmanuel Gautier ·
JWT

Les services backend modernes reçoivent rarement des tokens d’une source unique. Une plateforme B2B SaaS peut accepter des tokens utilisateurs issus de connexions SSO d’entreprise et de sa propre couche d’authentification. Une API gateway interne peut valider à la fois des tokens d’un CIAM provider côté utilisateur et d’un serveur OAuth machine-to-machine. Une plateforme en cours de migration peut avoir besoin d’honorer simultanément les tokens de l’ancien et du nouvel identity provider.

Chacun de ces scénarios pose la même question architecturale : comment valider correctement un JWT lorsque l’issuer varie selon la requête ? La réponse est directe — mais les modes d’échec si l’on s’y prend mal ne le sont pas.


Fonctionnement de la validation JWT

Si vous souhaitez en savoir plus sur la structure d’un JWT et sur la façon de le vérifier, consultez notre article sur comment vérifier un JWT.

Valider un JWT revient à répondre à quatre questions dans l’ordre :

  1. L’algorithme est-il acceptable ? N’autoriser que les algorithmes attendus. Ne jamais accepter alg: none.
  2. La signature est-elle valide ? Vérifier la signature contre la clé publique associée au kid du token, récupérée depuis le JWKS endpoint de l’issuer.
  3. Les claims sont-ils valides ? Vérifier exp, nbf, iss et aud — chacun d’entre eux.
  4. Le token est-il destiné à ce service ? Le claim aud doit correspondre à l’identifiant de votre service.

Avec un seul issuer, l’étape 2 est triviale : vous savez exactement où récupérer le JWKS. Avec plusieurs issuers, il faut une stratégie délibérée pour associer un token aux bonnes clés — et c’est là que la plupart des erreurs se produisent.


La bonne approche : un registre d’issuers pré-configuré

Le bon modèle mental consiste à traiter l’ensemble des issuers de confiance de votre service comme une liste fermée, configurée au moment du déploiement, et jamais dérivée à l’exécution depuis le token lui-même.

Chaque issuer de confiance possède une entrée fixe dans votre configuration, comprenant quatre éléments : l’URI exacte de l’issuer (valeur iss), le JWKS endpoint pré-configuré, l’audience attendue pour ce service, et la liste des algorithmes de signature autorisés. Rien dans cette liste ne peut être introduit ou modifié par une requête.

Le flux de validation pour chaque token entrant suit la séquence illustrée ci-dessous :

Flux de validation JWT multi-issuer

  1. Décoder le header et le payload du JWT (sans vérification pour l’instant)
  2. Lire le claim iss depuis le payload
  3. Rechercher l’issuer dans le registre → si absent, rejeter immédiatement
  4. Récupérer le JWKS URI depuis l’entrée pré-configurée pour cet issuer
  5. Récupérer le JWKS et trouver la clé correspondant au kid du token
  6. Vérifier la signature en utilisant uniquement cette clé
  7. Valider exp, nbf, aud en utilisant l’audience attendue par issuer
  8. Accepter ou rejeter selon la validité des claims

La contrainte critique est mise en évidence en violet dans le schéma : le JWKS URI vient de votre configuration, jamais du token. La valeur iss du token sert uniquement de clé de lookup dans votre registre — elle ne déclenche jamais de requête réseau par elle-même.

Mettre le JWKS en cache correctement

Les JWKS endpoints publient des clés publiques qui changent au fil du temps. Mettez agressivement en cache (une TTL d’une heure est habituelle), mais implémentez un fallback pour la rotation des clés : en cas d’échec de vérification de signature avec une clé en cache, invalidez le cache et refetchez une seule fois avant de rejeter le token. Cela gère la rotation des clés sans ouvrir de fenêtre pour un empoisonnement de cache. Le JWKS URI utilisé pour ce refetch vient toujours de votre configuration — jamais du token.


Vulnérabilités de sécurité liées à une mauvaise gestion multi-issuer

Chaque écart par rapport à ce modèle correspond à une classe d’attaque réelle.

1. Résolution dynamique du JWKS depuis le token (jku / injection iss)

La vulnérabilité la plus critique est de laisser le token dicter où récupérer ses propres clés de vérification.

Résolution JWKS : approche sûre vs vulnérable

À gauche (approche correcte), le JWKS URI vient du registre d’issuers en configuration. À droite (approche vulnérable), le service construit l’URL du JWKS à partir du claim iss du token, ou depuis le paramètre d’en-tête jku (JWK Set URL) qu’autorise optionnellement le standard JWT.

L’attaque est directe : un attaquant forge un token avec iss: "https://attacker.com" ou positionne jku: "https://attacker.com/keys", héberge son propre JWKS à cette URL, et signe le token avec sa clé privée. Le service récupère la clé publique de l’attaquant pour « vérifier » le token, la vérification de signature réussit, et le token est accepté.

La correction : ne jamais construire de requêtes réseau à partir de valeurs dérivées du token. Désactivez explicitement le traitement des headers jku et x5u dans votre bibliothèque JWT. Utilisez uniquement les valeurs jwks_uri pré-configurées.


2. Mutualisation des jeux de clés entre issuers

Le schéma ci-dessous illustre la différence entre le binding correct par issuer et le pattern vulnérable des clés mutualisées.

Binding issuer-clé : par issuer vs mutualisé

À gauche, la configuration de chaque issuer est exclusivement liée au jeu de clés de cet issuer. À droite, toutes les clés publiques de tous les issuers sont fusionnées en un seul pool, et la vérification de signature s’effectue contre ce pool sans conserver l’information de quelle clé appartient à quel issuer.

L’attaque : un token légitime de l’issuer A, scopé à l’audience A, est vérifié avec succès. Mais si la validation du claim iss intervient après la vérification de signature et se contente de vérifier que iss est un issuer de confiance, un attaquant peut modifier le claim iss vers l’issuer B. Plus subtilement, si deux issuers partagent une clé, un token de l’un peut être accepté comme s’il venait de l’autre.

La correction : chaque clé doit être liée à son issuer au moment de la vérification. Validez la signature et iss de façon atomique, en utilisant uniquement les clés appartenant à l’issuer configuré.


3. Correspondance d’issuer par préfixe ou wildcard

La comparaison d’issuer doit être une égalité stricte de chaîne — pas de préfixe, suffixe ou regex. Si https://auth.acme.com est de confiance et que la vérification utilise startsWith, un attaquant enregistre https://auth.acme.com.evil.com et la vérification de préfixe passe. Le claim iss d’un JWT est un URI ; traitez-le comme une chaîne à correspondance exacte, sensible à la casse, sans normalisation ambiguë de slash final.


4. Confusion d’algorithme

Les algorithmes autorisés doivent venir de votre configuration d’issuer, jamais du header alg du token. Les deux variantes classiques sont alg: none (qui désactive entièrement la vérification de signature) et la confusion RS256→HS256 : les clés publiques RS256 étant publiques par définition, un attaquant positionnant alg: HS256 peut amener certaines bibliothèques à utiliser la clé publique RS256 comme secret HMAC — valeur que l’attaquant connaît également — pour produire une signature HMAC valide.

Chaque entrée d’issuer dans votre registre déclare exactement les algorithmes acceptables pour les tokens de cet issuer. Rejetez tout ce qui ne correspond pas, indépendamment de ce que le token affiche dans alg.


5. Validation d’audience absente ou incorrecte

Valider iss et la signature sans vérifier aud laisse une faille critique. Un token légitimement émis par un issuer de confiance pour un autre service — par exemple votre dashboard de facturation — peut être présenté à votre API. L’issuer est de confiance, la signature est valide, l’expiration est dans le futur. Sans validation d’audience, votre API accepte un token qui ne lui était pas destiné.

C’est particulièrement dangereux sur les plateformes multi-tenant où le même IdP émet des tokens pour plusieurs services et où l’audience est le seul discriminant par service.

Chaque entrée d’issuer porte sa propre valeur audience attendue. Un token valide pour api://billing ne doit pas être accepté par api://provisioning.


Checklist

Avant de mettre en production une validation JWT multi-issuer, vérifiez chaque point :

  • Les issuers de confiance sont configurés statiquement — aucun issuer ne peut être introduit à l’exécution par un token
  • Le JWKS URI de chaque issuer est pré-configuré — jamais dérivé de iss, jku ou x5u
  • La comparaison d’issuer utilise une égalité stricte — pas de préfixe, suffixe ou regex
  • Les clés sont scopées par issuer — jamais mutualisées entre issuers lors de la vérification de signature
  • Les algorithmes autorisés sont configurés par issuer — jamais lus depuis le header alg du token
  • aud est validé sur chaque token, en utilisant l’audience attendue par issuer
  • exp et nbf sont toujours vérifiés
  • Le cache JWKS inclut une tentative de retry sur rotation de clé en cas d’échec de signature
  • Le traitement des headers jku et x5u est explicitement désactivé dans votre bibliothèque JWT

Résumé

Prendre en charge plusieurs issuers n’est pas intrinsèquement risqué. Le risque vient du fait de laisser le token piloter une partie de sa propre validation — quelle clé récupérer, quel algorithme utiliser, quel issuer faire confiance. Déplacez tout cela dans la configuration côté serveur. Le token est une entrée non fiable ; votre registre d’issuers est une configuration fiable. Gardez-les séparés, et la validation multi-issuer est aussi sûre que la validation mono-issuer.