Accueil > OpenID Connect OAuth Server par DnC > Développer > JSON Web Token (JWT)

Lorsque l’on utilise les flux du protocole OpenID Connect, une application peut obtenir un jeton d’identité (ID Token) au format JWT, en parallèle avec le jeton opaque normal.

Signé cryptographiquement, le JWT lie l’identité de l’application, celle de l’utilisateur final et les portées d’autorisation.

Après avoir décrit le jeton JWT, nous indiquons comment le vérifier et le consommer.

Voyez ici ce que l’on peut faire avec : Un jeton d’identification peut être utilisé pour ....

JSON Web Token (JWT)

  publié le par DnC

JWT est un standard ouvert (RFC 7519) qui définit une manière compacte et autonome de transmission sécurisée d’informations entre les parties sous la forme d’un objet JSON.

Les documents suivants spécifient comment le jeton JWT est mis en oeuvre :
- pour OAuth 2.0 : RFC 7523
- pour OpenID Connect : OpenID Connect Core 1.0 incorporating errata set 1.

Structure du Jeton JWT

Un jeton JWT se compose de trois parties séparées par des points (.) :

<header>.<payload>.<signature>

Chacune de ces trois composantes est codé Base64Url. OAuth 2.0 Server PHP utilise les fonctions suivantes :

PHP

  1. /**
  2. * @author    Brent Shaffer <bshafs at gmail dot com>
  3.  * @license   MIT License
  4. */
  5.  
  6.     public function urlSafeB64Encode($data)
  7.     {
  8.         $b64 = base64_encode($data);
  9.         $b64 = str_replace(array('+', '/', "\r", "\n", '='),
  10.                 array('-', '_'),
  11.                 $b64);
  12.  
  13.         return $b64;
  14.     }
  15.  
  16.     public function urlSafeB64Decode($b64)
  17.     {
  18.         $b64 = str_replace(array('-', '_'),
  19.                 array('+', '/'),
  20.                 $b64);
  21.  
  22.         return base64_decode($b64);
  23.     }

Télécharger

Bien que son format compact incite à le transmettre par URL, il est recommandé de le transmettre par Header dans l’en-tête Authorization avec le mécanisme d’authentification Bearer :

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImRlbW8iLCJmdWxsTmFtZSI6

Header : Entête

L’en-tête se compose généralement d’un array JSON de deux membres :

Alg : le type du jeton, qui est donc JWT
Typ : l’algorithme de hachage utilisé, comme HMAC SHA256 ou RSA.

Pour coder/décoder la signature, OAuth Server by DnC utilise par défaut l’algorithme HS256 et peut utiliser HS384 ou HS512 ainsi que RS256, RS384 et RS512.

Par exemple :

{
 "typ": "JWT",
  "alg": "HS256"
}

Paylod : Charge utile

La charge utile contient les déclarations sous le format JSON.

Les déclarations (claims) sont des informations sur une entité (généralement, l’utilisateur) et des métadonnées supplémentaires.

Il existe trois types de déclarations : reserved, public et private.

Déclarations réservées

Ces déclarations sont réservées, en ce sens que leur nom ne doit pas être utilisé par d’autres déclarations qui pourraient être définies dans le cadre d’un protocole ou d’une application particuliers.

Elles sont prédéfinies par le standard JWT (voir Registered Claim Names).

Format des déclarations réservées

Les déclarations réservées propres à JWT sont les suivantes (voir JWT Format and Processing Requirements) :

1. Le jeton JWT DOIT contenir une déclaration "iss" (émetteur) contenant l’identifiant unique de l’entité qui a émis le jeton.

2. Le jeton JWT DOIT contenir une déclaration "sub" (subject) identifiant le Principal qui est le sujet de la JWT. Deux cas doivent être différenciés :

A. Pour Authorization Grant, le sujet identifie généralement un accesseur autorisé pour lequel le jeton d’accès est demandé (c’est-à-dire le propriétaire de la ressource ou un délégué autorisé), mais dans certains cas, ce peut être un identifiant pseudonyme ou une autre valeur indiquant un utilisateur anonyme. [1]

B. Pour Client Authentication, le sujet DOIT être le "client_id" du client OAuth.

3. Le jeton JWT DOIT contenir une définition "aud" (audience), audience(s) pour laquelle (ou lesquelles)ce jeton d’identité est destiné. Il DOIT contenir le client_id OAuth 2.0 de la partie de confiance en tant que valeur d’audience. [2]

4. Le jeton JWT DOIT contenir une définition "exp" (expiration time) qui limite la fenêtre de temps pendant laquelle le JWT peut être utilisé.

5. Le JWT PEUT contenir une définition « nbf » (not before) qui identifie l’instant avant lequel le jeton NE DOIT PAS être accepté.

6. Le JWT PEUT contenir une définition "iat" (issued at) qui identifie l’instant auquel le jeton a été délivré.

7. Le jeton JWT PEUT contenir une définition "jti" (JWT ID) qui fournit un identifiant unique pour le jeton. Si c’est le cas, le serveur OAuth Server by DnC s’assure qu’un jeton JWT n’est pas réutilisé pendant sa durée de validité.

Le jeton JWT peut également présenter une déclaration at_hash, voir : Validation du jeton d’accès avec la déclaration at_hash du jeton d’identité.

Déclarations publiques

Elles peuvent être définies à volonté. Mais pour éviter les collisions, leurs noms devraient être choisis dans le IANA JSON Web Token Registry mentionné précédemment ou être définis avec la déclaration d’un espace de noms.

Déclarations privées

Ce sont des déclarations personnalisées créées pour partager des informations entre les parties qui ont prévu de les utiliser.

Exemple de déclarations :

{  
 "iss": "https:\/\/oa.dnc.global",
 "iat":1410255630,
 "exp":1410259230,
 "sub": "a394190a00f2d36b21309036b0b",
 "aud":"_c036b0b6f97a394190a00f2d36b213091938d225"
}

Notes :
- Il doit être bien compris que l’en-tête et la charge utile sont de simples chaînes codées en base64, qui peuvent facilement être déchiffrées et lues. Il ne doit donc pas y figurer de donnée sensible.

Signature

La signature est utilisée pour vérifier que l’expéditeur du jeton JWT est bien celui qu’il prétend être et de veiller à ce que le message n’a pas été modifié en cours de route.

Le JWT est signé en utilisant un secret (avec l’algorithme HMAC) ou une paire de clés publique/privée en utilisant RSA.

C’est cette deuxième solution qui est définie par OpenID Connect (la signature par HMAC est jugée insuffisamment sécurisée). Lorsque l’application passe le jeton à un serveur de ressource protégée (RS), il est impératif que le serveur de ressource valide le jeton. Le serveur de ressource (RS) qui valide la signature doit être en possession de la clé publique correspondant à la clé privée avec laquelle la signature a été élaborée. Il est également possible, voire avantageux, de faire valider le jeton par le serveur d’authentification (voir : API Open ID Connect : Introspection).

La paire de clés publique/privée doit avoir été définie par l’auteur au moment de l’inscription de l’application cliente sur le serveur OAuthSD.

Remarque de sécurité

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.

Notes

[1à faire : rédiger sous une forme plus compréhensible et plus proche du vocabulaire utilisé par ailleurs.

[2La spécification prévoit : "Il PEUT aussi contenir des identifiants pour d’autres audiences. Dans le cas général, la valeur aud est un tableau de chaînes sensibles à la casse. Dans le cas particulier où il n’y a qu’une audience, la valeur aud PEUT être une chaîne sensible à la casse". La bibliothèque oauth2-server-php réduit "aud" à l’id du client client_id.

Validation du jeton JWT

  publié le par DnC

Les JWT ne doivent jamais être approuvés tels quels. Les JWT 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 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, 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 d’accès 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 .

Procédure pour la Validation et la Consommation du jeton JWT

La validation d’un jeton d’identification 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 réclamation 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 de la signature du jeton d’identité JWT

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

Pour en savoir plus sur la validation du JWT :

- API Open ID Connect : Introspection
- 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.

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. 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.

Emettre un jeton d’accès en tant que JWT

  publié le par DnC

La bibliothèque oauth2-server-php de Brent Shaffer permet d’émettre un jeton d’accès en tant que JWT.
Avec cette fonctionnalité, les flux de codes d’autorisation OAuth 2.0 et OpenID Connect semblent très similaires, cependant ...

Voyez : https://bshaffer.github.io/oauth2-server-php-docs/overview/jwt-access-tokens/

Configuration du serveur d’autorisations pour le jeton d’accès JWT

Dans la bibliothèque, cela se fait en définissant le paramètre de configuration "use_jwt_access_tokens" à ’true’ :

  1. $config = array(
  2.     ...
  3.     'use_jwt_access_tokens' => true,
  4.     ...
  5. );

Télécharger

Pour la configuration de OAuthSD, le fichier /commons/configure_oauth.php et le fichier configure_oidc.php définissent tous deux la constante USE_JWT_ACCESS_TOKENS, fixée à ’false’ par défaut.

Avec les flux OpenID Connect, lorsqu’il est configuré sur ’true’, le contrôleur Token émet à la fois le jeton d’accès et le jeton d’identification en tant que JWT.

  1.   access_token: string = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6ImYzNDExNjM3Mjc2ZjJjMzUxZjczYz
  2. ...
  3. VFx1Rk972S4ON1Dn6FwadTDS2U_A"
  4.   expires_in: long = 3600
  5.   token_type: string = "bearer"
  6.   scope: string = "openid profile"
  7.   id_token: string = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJvMnNwLmRuYy5nbG9iYWwiLCJzdW...
  8. m6TFdfGhgjPyIA0w"

Télécharger

Use Case

Ecoutons Brent Shaffer [1] :

"vous pouvez utiliser les jetons d’accès JWT si vous avez des systèmes distribués et que vous devez valider l’authenticité d’un jeton auprès de plusieurs parties sans avoir à passer un appel réseau.

Par exemple, un jeton est attribué par le serveur d’autorisations. Ce jeton est un jeton Web JSON signé par la clé privée du serveur d’autorisations. Les serveurs de ressources (où les appels d’API sont effectués) sont répartis dans le monde entier et exécutent plusieurs applications. Tant que les serveurs de ressources disposent de la clé publique du serveur d’autorisations, qui n’a pas besoin d’être sécurisée, ils peuvent valider les jetons rapidement sans aucun appel réseau. Les jetons n’ont même pas besoin d’être persistés.

C’est un cas d’utilisation d’entreprise, mais il est très utile pour les systèmes distribués.."

Discussion

Cette fonctionnalité a été définie en 2014, alors qu’OpenID Connect était en construction. Il visait probablement à donner aux utilisateurs d’OAuth2 la capacité de valider les jetons d’accès dans les serveurs de ressources.

Il faut également considérer le problème posé par OAuth : ce n’est pas un système d’authentification (mais d’autorisation) parce que le jeton d’accès est opaque et ne donne pas d’information sur l’utilisateur final. En incorporant l’ID de l’utilisateur dans le jeton JWT, on fait d’OAuth un système d’authentification ce qui permet de passer l’information sur l’utilisateur à des ressources protégées. C’est ce que Facebook fait avec ce qu’ils appellent le "signed request".

Avec cette fonctionnalité, les flux de codes d’autorisation OAuth 2.0 et OpenID Connect semblent très similaires.
Il faut cependant noter la différence entre jeton d’accès et jeton d’identité : ils ne sont notamment pas liés dans le temps. La notion de "session OIDC" repose sur la validité du jeton d’identité qui expire, alors que le jeton d’accès expire indépendamment, peut être rafraîchi ou peut ne pas expirer. Cette utilisation du jeton d’accès en tant que JWT ne peut donc remplacer le jeton d’identité d’OpenID Connect.

Les jetons JWT peuvent être utilisés en tant qu’OAuth 2.0 Bearer Tokens pour coder toutes les parties pertinentes d’un jeton d’accès dans le jeton d’accès lui-même au lieu d’avoir à les stocker dans une base de données. Cela pourrait s’avérer très utile avec les applications sans session (comme les applications à page unique), avec une latence réduite pour la validation de jeton.

Incorporer au jeton JWT des déclarations supplémentaires

  publié le par DnC

Les spécifications d’OAuth 2.0 autorisent l’ajout de nouvelles déclarations (claims) à la charge utile du jeton JWT.
Cet article montre comment cela est réalisé dans le cadre d’OAuthSD.

Cas d’usage

Il convient de s’assurer que l’utilisateur final est bien habilité à accéder à des ressources protégées, qu’il s’agisse de données personnelles ou que l’application soit propriétaire des données. On voudrait également pouvoir contrôler les données transmises par une ressource protégée à une application en fonction des droits de l’utilisateur final dans cette application.

Les administrateurs aussi bien que les utilisateurs finaux utiliseront OpenID Connect pour s’authentifier sur une application. On voudra donc moduler les droits et privilèges de l’utilisateur final en fonction de son identité, de son appartenance ou de son profil. L’exemple le plus simple est la distinction entre des droits de lecture des données et des droits de modification. On peut également envisager d’attribuer à l’utilisateur des droits d’administration de tout ou partie d’une application.

Ce n’est pas le rôle du serveur d’authentification, qui se doit d’être transparent par rapport aux portées d’autorisation (scopes) mis en oeuvre par une application. Pour cela, OAuthSD permet à une application externe d’incorporer des déclarations supplémentaires aux jetons JWT, qu’il s’agisse d’un jeton d’accès au format JWT ou d’un jeton d’identité. Les ressources protégées "sauront" interpréter les données du JWT pour contrôler leur réponse.

Remarque quant à la sécurité des données

Rappelons que la charge utile du jeton JWT n’est pas cryptée, mais seulement "URL64-encoded". Il convient donc de ne pas transmettre de cette façon des données sensibles.

L’intérêt de passer des données supplémentaires dans la charge utile du jeton JWT est de les lier de façon infalsifiable à l’identité du client et à celle de l’utilisateur à l’aide de la signature. Cela est particulièrement pertinent pour transmettre les privilèges d’un utilisateur ou d’une application à une ressource protégée. Dans cette optique, on peut :
- soit transmettre directement les privilèges ; par exemple un rôle qui prendrait des valeurs telles que ’administrateur’, ’redacteur’, ’propriétaire’, ’utilisateur’, etc., ou tout autre code compris par la ressource,
- soit transmettre les informations sur l’utilisateur qui permettront à la ressource protégée d’établir ses privilèges.

Mais attention dans ce deuxième cas : il ne faudra pas dévoiler de données sensibles sur l’utilisateur. Si la transmission de telles données à la ressource protégée s’avérait indispensable, la ressource protégée pourra interroger le service Userinfo.
On pourrait penser à un jeton crypté (JWE), mais cela nécessiterait, outre un développement spécifique, de s’écarter du standard OpenID Connect. Il serait beaucoup plus simple de crypter les informations supplémentaires en un seul bloc et de les passer dans une déclaration unique.

Transmission de privilèges de l’utilisateur final avec l’authentification

OAuthSD offre deux méthodes complémentaires pour incorporer dans le JWT des informations relatives à un utilisateur final :

- l’écriture de données supplémentaires dans les champs ’profile’ et ’scope’ de la table users à l’aide du service HTTP Rest. Il s’agit d’un processus asynchrone dans la mesure où les données sont inscrites à un moment choisi par une application extérieure, indépendamment de l’interrogation du serveur d’autorisation.

OAuthSD utilise l’étendue d’autorisation (scope) "privileges" pour contrôler l’accès à ces informations. Quand une application présente le scope ’privileges’ dans la demande d’autorisation, les déclarations ’profile’ et ’scope’ sont inscrites dans la réponse Userinfo ainsi que dans la charge utile du jeton JWT.

- l’incorporation de données supplémentaires rafraîchies au moment de la fabrication du jeton JWT, par exemple en interrogeant un service externe. Il s’agit d’un processus synchrone ou de "scope dynamique", permettant d’obtenir des informations en phase avec celles détenues par les applications de gestion des utilisateurs dans le cas où cette gestion est extérieure au serveur.

Cette deuxième méthode est développée maintenant.

Incorporer des données supplémentaires à la charge utile du jeton JWT

Cette fonctionnalité est propre à une utilisation d’OAuthSD dans un groupe d’applications maîtrisées par une même organisation (Corporate Realm).

La bibliothèque oauth2-server-php offre une fonction reconfigurable pour insérer des déclarations supplémentaires dans un JWT au moment de sa création (depuis la version 1.10.0).

Pour incorporer des données supplémentaires, il faut :
- fixer la constante de configuration EXTRA_PAYLOAD à ’true’.
- définir le contenu de la fonction extra_payload() du fichier /commons/oidc/interface/extrapayload.php.
La fonction extra_payload() doit être écrite par le concepteur des applications. Ce peut être un appel à un webservice tiers. La fonction reçoit les paramètres client_id, user_id et scope et doit retourner un array de ’claim’ => ’valeur’ qui sera combiné avec les déclarations de la charge utile sans pouvoir remplacer les déclarations standard.

Notes :
- Cette méthode permet de créer des déclarations ayant un nom quelconque. Cependant, les déclarations standard d’OIDC (’id’, ’jti’, ’iss’, ’aud’, ’sub’, ’exp’, ’iat’, ’token_type’ et ’scope’) ne pourront être surchargées : elles garderont leur nom et leur valeur. Si une déclaration ’scope’ est retournée, elle sera incorporée à la charge utile du JWT sous le nom "extra_scope".

- Les informations sont rafraîchies au moment de la création du jeton JWT ou de son rafraîchissement. Cela implique que ces informations ne sont pas strictement synchrones de leur correspondantes dans le système extérieur. Si les droits d’un utilisateur changent dans le système extérieur, il conviendra de révoquer la session de l’utilisateur afin de provoquer une nouvelle demande d’authentification ce qui entraînera la création d’un nouveau jeton JWT. Mais dans le cas où il serait nécessaire de modifier un grand nombre d’utilisateurs, une autre façon d’assurer le synchronisme sera de fermer le service le temps de la durée de vie d’un JWT.

- Cette méthode n’écrit pas dans la table users et, en particulier, dans le champ "scope". Si les déclarations supplémentaires comprennent une déclaration "scope" (ce qui sera généralement le cas), la valeur du champ "scope" de la table users sera incorporée à la charge utile du JWT sous le nom "user_scope".

- Dans tous les cas, la valeur du paramètre ’scope’ passé à l’appel d’authorize par l’application sera incorporé sous le nom ’requested_scope’ [1].

Voyez ci-dessous le code d’un exemple extrayant d’un web-service des données supplémentaires.

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

Notes

[1Le nom "scope" est malheureusement utilisé par Oauth 2.0 pour des concepts différents. Il est donc naturel d’utiliser des noms différents pour éviter de mélanger ou d’écraser des données.

Un jeton d’identification peut être utilisé pour ...

  publié le par DnC

Dans la litérature sur OpenID Connect, le point de vue est souvent limité à l’identification centralisée (SSO ou single sign-on).
Un jeton d’identification est la carte d’identité de l’utilisateur final et le passeport de l’application cliente : il peut être utilisé à d’autres fins que la connexion de base.

Le jeton au format JWT présente les avantages suivants :
- C’est une spécification connue, largement adopté, et des bibliothèques client disponibles dans de nombreux langages (voyez Ressources pour les développeurs).
- Il facilite la signature et la vérification à l’aide de bibliothèques cryptographiques validées.
- Puisqu’il peut être décodé au format JSON, cela permet d’inclure des métadonnées et des informations sur le jeton, ainsi que des données complémentaires pour l’application à laquelle il est adressé.

Tant que le serveur d’autorisations et le serveur de ressources s’accordent sur la signification du jeton, le contenu n’a aucune importance. Ce qui signifie que soit on colle au standard OpenID Connect, et alors le serveur de ressource est limité à UserInfo, soit on est dans un espace propriétaire (corporate realm) où l’on fait ce que l’on veut.

On peut citer les applications suivantes :

Passage d’identité à des tiers - Le jeton ID peut être transmis à d’autres composants d’application ou à des services d’arrière-plan lorsque la connaissance de l’identité de l’utilisateur (end-user) est requise, par exemple pour identifier les enregistrements d’un historique.
Il est également possible à une application de passer le jeton pour obtenir l’accès à une ressource protégée, à charge pour celle-ci de le valider en mentionnant l’IP de l’appelant.
Il est possible de moduler la réponse de la ressource en fonction des privilèges de l’utilisateur en adaptant les scopes transmis à cet usage. Ceci est largement développé dans cet article : Définition des scopes et généralités sur leur utilisation par les applications et dans les articles connexes.

Sessions sans état - Le jeton d’identification peut être implémenté dans un cookie de navigateur. Cela supprime la nécessité de stocker des sessions côté serveur (en mémoire ou sur disque), ce qui peut être un fardeau pour la gestion et la montée en échelle. La session est vérifiée en validant le jeton d’identification. Lorsque le jeton atteint sa limite de validité (exp), l’application peut simplement en demander un nouveau à l’OP via une ré-authentification silencieuse prompt = none.
Cette utilisation du jeton est souvent associée à la gestion de session des applications à page unique (Single Page Application, SPA) [1].

Voici un exemple qui montre comment utiliser le jeton pour simuler une session en chaînant les pages avec l’id de l’utilisateur final (user_id) :

PHP

  1. ...
  2.  
  3. // Tenter de récupérer l'user_id depuis le JWT du cookie
  4.    
  5. $GLOBALS['user_id'] = null;
  6. $prompt = 'login';
  7.  
  8. $decoded_jwt = unserialize($_COOKIE['decoded_jwt']);
  9.    
  10.  if ( is_array($decoded_jwt)) {
  11.  
  12.         if ( $decoded_jwt['exp'] > time() ) {    
  13.  
  14.             // Le JWT du cookie est valide et nous donne l'user_id
  15.             $GLOBALS['user_id'] = $decoded_jwt['sub'];        
  16.  
  17.         } else {
  18.             // Demander un nouveau jeton sans réauthentification (prompt=none)
  19.             $prompt = 'none';
  20.         }      
  21.     }
  22.  
  23. if ( is_null($GLOBALS['user_id']) ) {  
  24.     // sinon, il faut demander un jeton avec authentification de l'utilisateur
  25. ...

Télécharger

DnC a développé des applications de ce type. Par exemple, photos.dnc.global est une application sans base de données, dont les sessions sont fondées sur le JWT en cookie. On comprend l’intérêt pour la montée en échelle : une telle application peut être répartie sur de nombreux serveurs sans que le développeur ait à se soucier de réplication des données.

Echange de jetons - Le jeton ID peut être échangé contre un jeton d’accès au point de terminaison Token d’un serveur d’autorisations OAuth 2.0 ( cf. Draft IETF : Token Exchange ). Il existe des scénarios réels où un document d’identité est requis pour obtenir l’accès, par exemple lorsque vous vous enregistrez dans un hôtel pour obtenir la clé de votre chambre. L’échange de jetons a des utilisations dans les applications distribuées et d’entreprise.

Notes

[1Pourquoi se limiter à la page unique ? Remarquons que cette technique est particulièrement avantageuse pour gérer des applications multi-pages et/ou un ensemble d’applications à page unique.

Plus loin avec l’Introspection

  publié le par DnC

En l’absence d’une norme stable, la bibliothèque oauth2-server-php de Brent Shaffer n’a pas de point d’extrémité Introspection.

OAuthSD, comme de nombreux serveurs d’autorisation, implémente son propre contrôleur pour l’Introspection. Il a été développé "au-dessus" de la bibliothèque.

Que pourrait être une "implémentation minimale et compatible" du contrôleur Introspection pour la bibliothèque ? Avant de commencer à coder, examinons le projet de RFC 7662 OAuth 2.0 Token Introspection.

À propos de la nécessité pour le Serveur de Ressource de s’authentifier

Selon rfc7662 Section 2.1. :

"L’appelant DOIT avoir une autorisation pour accéder à ce point de terminaison".

(Ce que nous aurions pu faire pour se conformer à cette spécification :
Cette implémentation attend un jeton d’accès émis pour un client enregistré (non public).
La provenance de ce jeton d’accès dépend du service.
Il a peut-être été obtenu du serveur d’autorisation par l’appelant en tant qu’application cliente.
Ou bien, si l’appelant est un serveur de ressources interrogé par une application cliente, le jeton lui a peut-être été transmise par l’appelant.)

Nous soumettons à la sagacité du lecteur les observations suivantes :

- Le but de cette autorisation est "Pour empêcher les attaques par balayage de jetons".
Ce type d’attaques est généralement géré au niveau du pare-feu en relation avec le service.
Par exemple, avec Apache, nous pourrions définir une authentification HTTP Basic au niveau du répertoire.
Ensuite, sur un serveur géré WHM / cPanel, nous pourrions utiliser CSF / LFD pour bloquer les échecs de connexion répétitifs.
Nous pourrions également configurer à cette fin une règle Apache Modsec particulière.
Plus généralement, l’attenuation des attaques est mieux effectuée en amont plutôt qu’au niveau de l’application. Cela induit une charge de calcul moindre, voire nulle, sur le serveur d’autorisations.

- Pour que le serveur de ressources s’authentifie, il doit être enregistré en tant qu’application cliente [1], ce qui suppose qu’il appartient au domaine de l’entreprise.
Ceci est vrai dans la plupart des cas, mais nous pouvons désirer que tout serveur de ressources étranger puisse vérifier un JWT.

À propos du but de l’Introspection

Valider la signature
La charge utile du jeton JWT n’est pas cryptée, seulement encodée. Son contenu est immédiatement lisible par le RS avant la validation de la signature.
Donc, le RS a besoin d’une introspection pour vérifier que la signature est valide (si elle ne la valide pas localement en utilisant la clé publique).

Authentifier l’application qui appelle le serveur de ressource (RS)
De plus, le but de transmettre un jeton à un RS est de lui indiquer s’il peut répondre à l’application requérante et quelles données il peut lui transmettre dans sa réponse.
Un attaquant pourrait avoir volé un JWT et l’utiliser avec sa propre application. Le risque est que le RS réponde à une application et / ou à un utilisateur non autorisé. Il est donc essentiel d’authentifier l’application qui interroge le RS pour l’empêcher de répondre à un malware (voir plus loin).

Cela dit, quelle tâche spécifique doit être accomplie par l’Introspection et quelle doit être la réponse ?

À propos de la réponse de l’Introspection

Selon la section 2.2. de rfc7662, le seul membre requis est "actif", un indicateur booléen indiquant la validité du jeton vu depuis le serveur qui l’a émis.
Cela va au-delà de la validation de la signature car cela "indique qu’un jeton donné a été émis par ce serveur d’autorisation, n’a pas été révoqué par le propriétaire de la ressource, ...".

Mais inclure dans cette valeur la condition "et se trouve dans sa fenêtre temporelle de validité donnée" ne parait pas pertinent : le RS possède déjà les informations de la charge utile. En revanche, le jeton pourrait avoir été invalidé au niveau du serveur, et c’est cette information que l’introspection apportera. Au passage, notons qu’il n’y a pas de lien entre la durée de vie du jeton d’accès et celle du jeton d’identité. Il y a une possibilité de révoquer le jeton d’accès, mais qu’en est-il du jeton d’identité ?

Notez que l’Introspection devrait également être utilisable pour valider un jeton d’accès JWT, avec des membres moins nombreux et différents. C’est une autre raison pour ne pas spécifier les membres de façon autoritaire.

Empêcher le Serveur de Ressource de répondre à un malware

C’est une question centrale !

La spécification reconnaît la question, mais ne donne pas une réponse normalisée. Il est indiqué dans la section 2.1 :

"Le point de terminaison d’introspection PEUT accepter d’autres paramètres OPTIONNELS pour fournir un contexte supplémentaire à la requête. Par exemple, un serveur d’autorisation peut souhaiter connaître l’adresse IP du client accédant à la ressource protégée pour déterminer si le client présentant le jeton est le bon. La définition de ce paramètre ou de tout autre paramètre sort du cadre de la présente spécification, ... "

Nous proposons mettre ceci en œuvre de la manière suivante :
Si le RS appelant le point de terminaison Introspection transmet l’adresse IP de son propre demandeur dans le ’requester_ip’, nous devons vérifier que cette adresse IP se trouve dans le même sous-réseau que l’application client identifiée par la déclaration ’aud’. Voyez : Vérification de l’origine de la requête reçue par un serveur de ressource.

À propos de l’adaptation de la réponse au Serveur de Ressource

Il est indiqué à la section 2.2 :

"Le serveur d’autorisation PEUT répondre différemment à différentes ressources protégées qui font la même demande. Par exemple, un serveur d’autorisation PEUT limiter les étendues d’un jeton donné qui sont renvoyées à chaque ressource protégée afin d’empêcher une ressource protégée d’en apprendre davantage sur l’ensemble plus vaste d’applications du réseau que nécessaire pour son fonctionnement. "

Cela semble très difficile à mettre en œuvre. Dans l’exemple donné, qui traite des portées, le RS doit faire appel à l’introspection avec plus de paramètres ou le AS doit avoir des informations sur le RS. En somme, il s’agit de prendre en compte les scopes au niveau de l’introspection pour définir les autorisations.

Cette prescription peut être appliquée dans un domaine d’entreprise pour des applications particulières, elle ne sera donc pas prise en compte dans notre "implémentation minimale et compatible".

Nous pensons que les portées définissant les droits ont une signification qui devrait être interprétée par la ressource protégée elle-même, le serveur d’authentification devant les transmettre de manière transparente.

... ou ne répond pas du tout

Il est indiqué dans la section 4 :

"Si le jeton ne peut être utilisé que sur certains serveurs de ressources, le serveur d’autorisation DOIT déterminer s’il peut ou non être utilisé sur le serveur de ressources effectuant l’appel d’introspection".

Cette prescription n’est rien de plus qu’une déclinaison radicale de la précédente. Nous allons la traiter de la même manière.

Proposition d’un contrôleur d’introspection pour la bibliothèque

Une "implémentation minimale et compatible" du contrôleur Introspection aurait les caractéristiques suivantes :
- se limitera à la validation de la signature JWT avec l’option de vérification de l’adresse IP du client (paramètre ’requester_ip’), sans aucune hypothèse quant à l’utilisation du jeton,
- ne nécessitera pas l’authentification de l’appelant,
- acceptera le jeton porteur de préférence pour poster un jeton,
- peut répondre comme indiqué dans le RFC.

 Suivez ce développement sur GitHub
Voyez le fork :
https://github.com/bdegoy/oauth2-server-php
Testez avec :
https://github.com/bdegoy/oauth2-server-php-introspection-test
Discutez-en :
https://github.com/bshaffer/oauth2-server-php/pull/964

Notes

[1Ceci est l’option prise par IBM