Accueil > OpenID Connect OAuth Serveur dédié > Développer > Validation du jeton d’identité ID Token (JWT signé ou JWS)

Validation du jeton d’identité ID Token (JWT signé ou JWS)

Les Jetons d’identité ne doivent jamais être approuvés tels quels. Les jetons peuvent être réutilisés par un malware, interceptés ou falsifiés par des attaquants. Lorsqu’une application ou une ressource protégée reçoit un jeton d’Identité JWT, elle doit toujours le valider.
Cependant, valider le jeton n’est pas tout : il faut encore vérifier qu’il est présenté par une application qui le détient légitimement.

Problématique

Le jeton d’identité, de type JWT signé (JWS), doit être validé dans deux situations :

- Dès sa réception, conformément à ce qui est défini dans la spécification d’OpenID Connect [1].

- Pour autoriser l’accès à une ressource protégée. Voyez comment le problème se pose de façon générale : Validation du jeton par une ressource protégée.
Deux cas se présentent :
- soit on passe la clé publique au serveur de ressource protégée RS qui procède localement à sa validation ; si ce RS n’est pas lié à l’organisation qui contrôle le serveur d’authentification, il peut utiliser la fonction API OpenID Connect : Découverte,
- soit on utilise une méthode dite "introspection" consistant à demander l’authentification du jeton JWT au serveur d’authentification qui l’a délivré : API Open ID Connect : Introspection (Introspection Endpoint) .

Procédure pour la Validation et la Consommation du jeton JWT signé (JWS)

La validation d’un jeton d’identité nécessite plusieurs étapes. Que le jeton soit validé du côté du serveur d’authentification ou à distance, la méthode est la même.

Le décodage et la validation du jeton suivent la méthode définie ici : Spécification OpenID Connect : Validation du jeton d’identité :

Les clients DOIVENT valider le jeton ID dans la réponse au jeton de la manière suivante :

- Si le jeton d’identification est chiffré, déchiffrez-le à l’aide des clés et des algorithmes spécifiés lors de l’enregistrement par le client, que l’OP devait utiliser pour chiffrer le jeton d’identification. Si le chiffrement a été négocié avec l’OP au moment de l’enregistrement et que le jeton d’identification n’est pas chiffré, le RP [2] DEVRAIT le rejeter.
- L’identifiant de l’émetteur pour le fournisseur OpenID (qui est généralement obtenu lors de la découverte) DOIT correspondre exactement à la valeur de la déclaration iss (issuer).
- Le client DOIT valider que la déclaration aud (audience) contienne la valeur client_id enregistrée auprès de l’émetteur identifié par la déclaration iss (émetteur) en tant qu’audience. La déclaration aud (audience) PEUT contenir un tableau avec plus d’un élément. Le jeton ID DOIT être rejeté si le jeton ID ne répertorie pas le client en tant qu’audience valide, ou s’il contient des audiences supplémentaires non approuvées par le client.
- Si le jeton d’identification contient plusieurs audiences, le client DEVRAIT vérifier qu’une déclaration azp est présente.
- Si une déclaration azp (partie autorisée) est présente, le client DEVRAIT vérifier que son client_id est la valeur de la déclaration.
- Si le jeton ID est reçu via une communication directe entre le client et le point d’extrémité du jeton (qui se trouve dans ce flux), la validation TLS du serveur PEUT être utilisée pour valider l’émetteur au lieu de vérifier la signature du jeton [3]. Le client DOIT valider la signature de tous les autres jetons ID conformément à JWS [JWS] en utilisant l’algorithme spécifié dans le paramètre d’en-tête JWT alg. Le client DOIT utiliser les clés fournies par l’émetteur.
- La valeur alg DEVRAIT être la valeur par défaut de RS256 ou l’algorithme envoyé par le client dans le paramètre id_token_signed_response_alg lors de l’enregistrement.
- Si le paramètre d’en-tête JWT alg utilise un algorithme basé sur MAC, tel que HS256, HS384 ou HS512, les octets de la représentation UTF-8 du secret client correspondant à l’identifiant client contenu dans la déclaration aud (audience) sont utilisés comme clé de validation de la signature. Pour les algorithmes basés sur MAC, le comportement n’est pas spécifié si l’aud est multivalué ou si une valeur azp est différente de la valeur aud.
- L’heure actuelle DOIT être antérieure à l’heure représentée par la déclaration exp.
- La déclaration Iat peut être utilisé pour rejeter des jetons qui ont été émis trop loin de l’heure actuelle, limitant ainsi la durée de stockage des clés pour prévenir les attaques. La plage acceptable est spécifique au client.
- Si une valeur nonce a été envoyée dans la demande d’authentification, une déclaration de nonce DOIT être présente et sa valeur vérifiée pour contrôler qu’il s’agit de la même valeur que celle qui a été envoyée dans la demande d’authentification. Le client DEVRAIT vérifier la valeur de nonce pour les attaques par relecture. La méthode précise pour détecter les attaques par relecture est spécifique au client.
- Si la déclaration acr a été mentionnée, le client DEVRAIT vérifier que la valeur de réclamation revendiquée est appropriée. La signification et le traitement des déclarations acr sont hors du domaine d’application de la présente spécification.
- Si la déclaration auth_time a été mentionnée, par le biais d’une demande spécifique pour cette déclaration ou du paramètre max_age, le client DEVRAIT vérifier la valeur de la déclaration auth_time et demander une nouvelle authentification s’il détermine qu’il s’est écoulé trop de temps depuis la dernière authentification de l’utilisateur final.

Exemple de code pour la vérification locale de la signature du jeton d’identité JWT signé (JWS)

La plupart de ces vérifications ne nécessitent qu’une simple comparaison de chaînes. La validation de la signature est plus complexe. L’implémentation qui en est faite par OAuthSD est décrite maintenant. La fonction suivante, tirée de OAuth 2.0 Server PHP, sépare les composantes du jeton, détecte les erreurs de format, vérifie éventuellement la signature et retourne la charge utile ou false en cas d’erreur.

La variable $key passe la clé publique qui a servi à générer le jeton JWT.

Si $key = null, la vérification de la signature n’est pas effectuée (on suppose le jeton déjà validé par introspection), et le contenu de la charge utile est retourné sous la forme d’un tableau associatif.

PHP

  1. /**
  2. * @author    Brent Shaffer <bshafs at gmail dot com>
  3.  * @license   MIT License
  4. */
  5.  
  6. /**
  7.     * Sépare les composantes du jeton, détecte les erreurs de format, vérifie la signature et retourne la charge utile ou false en cas d'erreur.
  8.     *
  9.     * @param mixed $jwt : le jeton JWT
  10.     * @param mixed $key : la clé publique
  11.     * @param mixed $allowedAlgorithms : un array des codes d'algorithmes autorisés (sous ensemble de HS256, HS384 ou HS512, RS256, RS384 et RS512). Si ce paramètre est précisé, le jeton doit indiquer l'algorithme et celui-ci doit être compris dans l'array.
  12.     * @param mixed return : charge utile (tableau associatif) ou false.
  13.     */
  14.  
  15. function decode($jwt, $key = null, $allowedAlgorithms = true)
  16.     {
  17.         if (!strpos($jwt, '.')) {
  18.             return false;
  19.         }
  20.  
  21.         $tks = explode('.', $jwt);
  22.  
  23.         if (count($tks) != 3) {
  24.             return false;
  25.         }
  26.  
  27.         list($headb64, $payloadb64, $cryptob64) = $tks;
  28.  
  29.         if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
  30.             return false;
  31.         }
  32.  
  33.         if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
  34.             return false;
  35.         }
  36.  
  37.         $sig = $this->urlSafeB64Decode($cryptob64);
  38.  
  39.         if ((bool) $allowedAlgorithms) {
  40.             if (!isset($header['alg'])) {
  41.                 return false;
  42.             }
  43.  
  44.             // check if bool arg supplied here to maintain BC
  45.             if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
  46.                 return false;
  47.             }
  48.  
  49.             if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
  50.                 return false;
  51.             }
  52.         }
  53.  
  54.         return $payload;
  55.     }
  56.  
  57. function verifySignature($signature, $input, $key, $algo = 'HS256')
  58.     {
  59.         // use constants when possible, for HipHop support
  60.         switch ($algo) {
  61.             case'HS256':
  62.             case'HS384':
  63.             case'HS512':
  64.                 return $this->hash_equals(
  65.                     $this->sign($input, $key, $algo),
  66.                     $signature
  67.                 );
  68.  
  69.             case 'RS256':
  70.                 return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256')  === 1;
  71.  
  72.             case 'RS384':
  73.                 return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
  74.  
  75.             case 'RS512':
  76.                 return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
  77.  
  78.             default:
  79.                 throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
  80.         }
  81.     }

Télécharger

Avertissement à propos du paramètre ’alg’

La spécification prévoit d’appliquer la valeur du paramètre ’alg’ pour le choix de l’algorithme de validation de la signature : RFC 7515, section 4.1.1. : "... This Header Parameter MUST be present and MUST be understood and processed by implementations ...".

C’est une faille de sécurité, et donc une erreur de la spécification. OAuthSD applique la méthode définie pour chaque application, quelle que soit la valeur de alg. Il est intéressant de constater que OAuthSD passe les tests de validation de l’OIDF comme cela.

En savoir plus sur la validation du JWT :

- API Open ID Connect : Introspection (Introspection Endpoint)
- API OpenId Connect : Point d’extrémité d’informations sur les clefs (Keys Endpoint)
- OpenID Connect : Exemples complets du flux d’Autorisation via un code puis requête UserInfo

Voyez également :
- Validation du jeton d’accès avec la déclaration at_hash du jeton d’identité.

Notons que la validation du jeton ne suffit pas au serveur de ressource pour s’assurer que l’application qui présente le jeton le détient légitimement et éviter de répondre à une application étrangère. Voir à ce sujet :
- Vérification de l’origine de la requête reçue par un serveur de ressource.

Il existe des informations complémentaires, connectez vous pour les voir.

Notes

[1Voici pourtant ce que dit Google : "Normalement, il est essentiel de valider un jeton d’identification avant de l’utiliser, mais puisque vous communiquez directement avec Google via un canal HTTPS sans intermédiaire et que vous utilisez le secret de votre client pour vous authentifier auprès de Google, vous pouvez être sûr que le jeton que vous recevez vient vraiment de Google et est valide." C’est à dire que le jeton peut être n’importe quoi, une tartine, un cafard ou un cheval, du moment que l’on a une réponse c’est bon !!! Autant dire que le jeton ne sert à rien. Tiens, on reconnait là l’erreur qui avait prévalu avec le jeton d’accès d’OAuth 2.0. Heureusement il y a la suite : "Si votre serveur transmet le jeton d’identification à d’autres composants de votre application, il est extrêmement important que les autres composants le valident avant de l’utiliser. " (https://developers.google.com/ident...).

[2Relying Party : l’application cliente ou le serveur de ressource protégée etc..

[3Autrement dit, si la liaison entre le client et le serveur est sécurisée par TLS, on pourrait se passer de valider la signature. C’est ce que dit Google ici : https://developers.google.com/ident... . Cependant, nous considérons qu’il faut toujours valider la signature du jeton quel que soit l’utilisation, et pas seulement dans le cas où le jeton est retransmis à une application ou ressource tierce.