Les applications se parlent en permanence, sur des réseaux privés et sur l’internet ouvert. Comment sécuriser ces échanges ? Comment savoir qui a envoyé un message ? Comment empêcher qu’il soit lu ou altéré en chemin, sans dévorer votre bande passante ? Les JSON Web Tokens répondent à tout cela, et davantage.
D’abord, cinq principes
La sécurité informatique repose sur une poignée de garanties. Cinq d’entre elles comptent ici.
- L’intégrité : les données n’ont pas été altérées. Les altérations peuvent être accidentelles, un protocole défectueux ou un signal corrompu, ou délibérées, une usurpation ou une injection de données forgées.
- La confidentialité : les données ne sont pas lisibles en transit. Pensez au formulaire de connexion que vous soumettez. Voudriez-vous que quelqu’un intercepte vos identifiants ?
- La disponibilité : le système fonctionne en toutes circonstances, sans panne ni interruption.
- La non-répudiation : l’origine des données est prouvable. L’expéditeur peut être identifié et ne peut pas nier l’avoir écrit.
- L’authentification : seul le destinataire prévu atteint les données. Aucune autre partie ne peut les lire sans permission explicite.
D’autres existent, la traçabilité par exemple, mais elles sortent du cadre ici.
Qu’est-ce qu’un JSON Web Token ?
Un JSON Web Token, ou JWT, est un format ouvert fondé sur JSON pour transférer des données de façon sûre entre deux applications. Il est conçu pour le web.
Il répond à chacun des principes ci-dessus. L’intégrité vient de la signature numérique des données. La confidentialité vient du chiffrement du jeton. La non-répudiation et l’authentification viennent des clés employées pour ces opérations. La disponibilité est servie par un format assez simple pour être traité rapidement.
Il existe plusieurs représentations, que nous croiserons en chemin, mais le plus souvent il ressemble à ceci :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
N’y avait-il pas d’autres moyens ?
Bien sûr que si, et le web n’a pas attendu ces jetons pour sécuriser ses échanges. Une connexion sécurisée en HTTPS en couvre déjà une partie.
Il y a aussi SAML, le Security Assertion Markup Language, un format XML dédié aux transferts sûrs qui offre les mêmes garanties. Il est très employé, notamment dans l’authentification unique.
Alors pourquoi un nouveau format ?
- La simplicité. La structure et la représentation d’un jeton sont sobres : pas d’ornement, pas de champ inutilisé.
- La compacité. Un JWT fait environ 500 octets là où une assertion SAML en prendrait plus de 2 500. Deux kilooctets semblent négligeables, mais sur un serveur qui traite des centaines de milliers d’échanges par seconde, cela fait plusieurs gigaoctets de bande passante économisés chaque jour.
- La facilité d’emploi. Quelques lignes de code créent et lisent un jeton en entier, et l’analyse prend des microsecondes avec les algorithmes les plus rapides.
Un bénéfice de plus
Une réduction drastique des appels à la base de données. Ce type de jeton porte en lui-même tout ce dont votre application a besoin, donc il n’y a pas d’entité à charger ni de requête à exécuter. Avec le temps, cela allège vos serveurs de façon appréciable, ce qui sert la disponibilité à son tour.
Où les emploie-t-on ?
Surtout dans les protocoles d’autorisation comme OAuth2, pour protéger une API REST par exemple, et dans les protocoles d’identité comme OpenID Connect. Rien ne les y limite : une messagerie sécurisée ou un échange de documents sont tout aussi légitimes.
Tout au long de cet article, les exemples sont en PHP, avec web-token/jwt-framework. D’autres existent pour PHP et pour d’autres langages, et jwt.io en tient une liste assez complète. J’emploie celle-ci pour deux raisons : je l’ai écrite, et c’est l’une des rares bibliothèques PHP, peut-être la seule, à implémenter toutes les spécifications couvertes ici. Si elle ne vous convient pas, ou si PHP n’est pas votre langage, employez ce que vous préférez pour la suite.
Un standard ouvert
L’Internet Engineering Task Force, l’IETF, est le groupe qui produit la plupart des standards de l’internet. Un standard passe par une phase d’écriture, de relecture et de correction à l’état de brouillon avant d’être soumis à approbation. Une fois approuvé, il devient une Request For Comments, une RFC, et reçoit un numéro dans l’ordre chronologique. RFC et brouillons sont librement accessibles.
Si des erreurs sont trouvées après approbation, un erratum est publié. Un standard peut aussi être mis à jour par un autre standard. L’IETF est ouverte à tous, vous compris, si vous souhaitez normaliser un protocole, ou simplement signaler une erreur dans une RFC.
Les JSON Web Tokens sont devenus des standards en mai 2015. Chaque aspect qui les entoure est décrit dans un document dédié :
- RFC 7515 : JSON Web Signature (JWS)
- RFC 7516 : JSON Web Encryption (JWE)
- RFC 7517 : JSON Web Key (JWK)
- RFC 7518 : JSON Web Algorithms (JWA)
- RFC 7519 : JSON Web Token (JWT)
D’autres RFC ont depuis complété cette liste, et nous en rencontrerons quelques-unes. Ces cinq-là sont les principales. Commençons par la RFC 7519, qui décrit la structure de base d’un JWT.
La charge utile
En théorie
La charge utile d’un JSON Web Token est, roulement de tambour, un objet JSON. Sans quoi ces jetons porteraient un autre nom.
C’est en général un ensemble de membres qui forment des revendications, chacune ayant un sens bien défini. La RFC 7519 en normalise un certain nombre pour que les applications se comprennent. Elle autorise aussi des membres propres à une application, tant qu’ils ne réemploient pas un nom standard et que les deux extrémités s’entendent sur leur sens.
Aucun des suivants n’est obligatoire. Les employer ou non dépend entièrement du contexte.
iss(Issuer) : qui a émis le jetonsub(Subject) : de quoi parle le jeton, un utilisateur, une ressourceaud(Audience) : à qui le jeton est destinéexp(Expiration Time) : quand il cesse d’être valablenbf(Not Before) : quand il commence à être valableiat(Issued At) : quand il a été émisjti(JWT ID) : un identifiant unique pour le jeton
Un exemple de charge utile :
{
"iss": "joe",
"exp": 1300819380,
"http://example.com/is_root": true
}
Ici l’émetteur est joe, le jeton a expiré le 22 mars 2011 à 19h43 UTC, et un membre propre à l’application, http://example.com/is_root, vaut vrai.
Attention à l’encodage des caractères : seul UTF-8 est autorisé. Si votre système en emploie un autre, convertissez.
En pratique
<?php
$data = [
'iss' => 'joe',
'exp' => 1300819380,
'http://example.com/is_root' => true,
];
echo json_encode($data);
Le résultat ne porte ni saut de ligne ni espace :
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
L’en-tête
En théorie
L’en-tête est du JSON lui aussi, sans surprise. Chacun de ses membres a un sens précis, et la RFC 7519 en définit deux :
typ, le type de média. Facultatif, et souvent omis parce que la valeur sera d’ordinaire JWT. L’employer est néanmoins vivement conseillé, pour que votre application distingue les jetons qu’elle reçoit.cty, le type de contenu. Facultatif également, et déconseillé sauf dans deux cas : le jeton est imbriqué, c’est à dire un jeton chiffré dont le contenu est un jeton signé, ou la charge utile a un type particulier, une clé JWK par exemple.
Si le type de média ou le type de contenu que vous employez commence par application/ et ne contient pas d’autre barre oblique, vous pouvez retirer le préfixe : application/secevent+jwt devient secevent+jwt.
La même RFC note que certains membres de la charge utile peuvent être dupliqués dans l’en-tête. Nous verrons plus loin pourquoi c’est utile.
{ "typ": "JWT" }
Ou bien :
{
"iss": "My Great Service",
"exp": 1502749759
}
Peu de choses à montrer, au fond. Deux en-têtes facultatifs, dont un déconseillé, et une charge utile dont les membres sont facultatifs eux aussi. Où est l’intérêt ?
L’intérêt des JSON Web Tokens tient à ce que vous pouvez leur faire : signer les données, ou les chiffrer. D’autres en-têtes existent pour ces opérations, et certains sont obligatoires. C’est là que cela devient intéressant, et c’est là que reprend la partie suivante.
En pratique
Rien d’exotique : l’en-tête se construit exactement comme la charge utile.
<?php
$data = [
'iss' => 'My Great Service',
'exp' => 1502749759,
];
echo json_encode($data);
Deux objets JSON, donc. Ce qui en fait un jeton est le sujet de l’article suivant.