Que sont les jetons Web JSON (JWT) ? Pourquoi les API les utilisent-elles ?
La norme JSON Web Tokens (JWT) décrit une méthode compacte pour les transferts de données vérifiables. Chaque jeton contient une signature qui permet à la partie émettrice de vérifier l’intégrité du message.
Dans cet article, vous apprendrez ce que comprend la structure JWT et comment vous pouvez générer vos propres jetons. Les JWT sont un moyen populaire de sécuriser les API et d’authentifier les sessions utilisateur, car ils sont simples et autonomes.
Sommaire
Comment fonctionnent les JWT
L’une des tâches les plus courantes de toute API consiste à valider que les utilisateurs sont bien ceux qu’ils prétendent être. L’authentification est généralement gérée en demandant au client d’inclure une clé API avec les requêtes qu’il envoie au serveur. La clé contient des informations intégrées qui identifient l’utilisateur. Cela laisse encore une grande question : comment le serveur peut-il valider qu’il a émis la clé en premier lieu ?
Les JWT résolvent facilement ce problème en utilisant un secret pour signer chaque jeton. Le serveur peut vérifier la validité d’un jeton en essayant de recalculer la signature présentée à l’aide de son secret privé. Toute altération entraînera l’échec de la vérification.
Le format JWT
Les JWT sont formés de trois composants distincts :
- Entête – Cela inclut les métadonnées sur le jeton lui-même, telles que l’algorithme de signature qui a été utilisé.
- Charge utile – La charge utile du jeton peut être n’importe quelle donnée arbitraire pertinente pour votre système. Il peut inclure l’identifiant de l’utilisateur et une liste des fonctionnalités avec lesquelles il peut interagir.
- Signature – La signature permet de valider ultérieurement l’intégrité du token. Il est créé en signant l’en-tête et la charge utile à l’aide d’une valeur secrète connue uniquement du serveur.
Ces trois composants sont associés à des périodes pour produire le JWT :
header.payload.signature
Chaque pièce est encodée en Base-64. Le jeton complet est une chaîne de texte qui peut être facilement consommée dans des environnements de programmation et envoyée avec des requêtes HTTP.
Créer un JWT
Les étapes de création d’un JWT peuvent être implémentées dans tous les langages de programmation. Cet exemple utilise PHP mais le processus sera similaire dans votre propre système.
Commencez par créer l’en-tête. Cela comprend généralement deux champs, alg
et typ
:
alg
– L’algorithme de hachage qui sera utilisé pour créer la signature. Il s’agit normalement de HMAC SHA256 (HS256
).typ
– Le type de jeton qui est généré. Cela devrait êtreJWT
.
Voici le JSON qui définit l’en-tête :
{ "alg": "HS256", "typ": "JWT" }
L’en-tête JSON doit ensuite être encodé en Base64 :
$headerData = ["alg" => "HS256", "typ" => "JWT"]; $header = base64_encode(json_encode($headerData));
Définissez ensuite la charge utile de votre jeton comme un autre objet JSON. Ceci est spécifique à l’application. L’exemple fournit des détails sur le compte d’utilisateur authentifié, ainsi que des informations sur le jeton lui-même. exp
, iat
et nbf
sont des champs utilisés par convention pour exprimer l’heure d’expiration du jeton, émis à l’heure, et non valide avant l’heure (de début). La charge utile doit également être encodée en Base64.
$payloadData = [ "userId" => 1001, "userName" => "demo", "licensedFeatures" => ["todos", "calendar", "invoicing"], "exp" => (time() + 900), "iat" => time(), "nbf" => time() ]; $payload = base64_encode(json_encode($payloadData));
Il ne reste plus qu’à créer la signature. Pour produire cela, vous combinez d’abord l’en-tête et la charge utile en une seule chaîne séparée par un .
personnage:
$headerAndPayload = "$header.$payload";
Ensuite, vous devez générer un secret unique à utiliser comme clé de signature. Le secret doit être stocké en toute sécurité sur votre serveur et ne doit jamais être envoyé aux clients. L’exposition de cette valeur permettrait à quiconque de créer des jetons valides.
// PHP method to generate 32 random characters $secret = bin2hex(openssl_random_pseudo_bytes(16));
Vous terminez le processus en utilisant le secret pour signer l’en-tête combiné et la chaîne de charge utile à l’aide de l’algorithme de hachage que vous avez indiqué dans l’en-tête. La signature de sortie doit être encodée en Base64 comme les autres composants.
$signature = base64_encode(hash_hmac("sha256", $headerAndPayload, $secret, true));
Vous avez maintenant l’en-tête, la charge utile et la signature en tant que composants textuels individuels. Rejoignez-les tous avec .
séparateurs pour créer le JWT à envoyer à votre client :
$jwt = "$header.$payload.$signature";
Vérification des JWT entrants
L’application client peut déterminer les fonctionnalités disponibles pour l’utilisateur en décodant la charge utile du jeton. Voici un exemple en JavaScript :
const tokenComponents = jwt.split("."); const payload = token[1]; const payloadDecoded = JSON.parse(atob(payload)); // ["todos", "calendar", "invoicing"] console.log(payloadDecoded.licensedFeatures);
Un attaquant pourrait réaliser que ces données sont en texte brut et semblent faciles à modifier. Ils pourraient essayer de convaincre le serveur qu’ils ont une fonctionnalité bonus en modifiant la charge utile du jeton dans leur prochaine requête :
// Create a new payload component const modifiedPayload = btoa(JSON.stringify({ ...payloadDecoded, licensedFeatures: ["todos", "calendar", "invoicing", "extraPremiumFeature"] })); // Stitch the JWT back together with the original header and signature const newJwt = `${token[0]}.${modifiedPayload}.${token[2]}`
La réponse à la façon dont le serveur se défend contre ces attaques réside dans la méthode utilisée pour générer la signature. La valeur de la signature prend en compte l’en-tête et la charge utile du jeton. La modification de la charge utile, comme dans cet exemple, signifie que la signature n’est plus valide.
Le code côté serveur vérifie les JWT entrants en recalculant leurs signatures. Le jeton a été falsifié si la signature envoyée par le client ne correspond pas à la valeur générée sur le serveur.
$tamperedToken = $_POST["apiKey"]; list($header, $payload, $signature) = $tamperedToken; // Determine the signature this token *should* have // when the server's secret is used as the key $expectedSignature = hash_hmac("sha256", "$header.$payload", $secret, true); // The token has been tampered with because its // signature is incorrect for the data it includes if ($signature !== $expectedSignature) { http_response_code(403); } // The signatures match - we generated this // token and can safely trust its data else { $user = fetchUserById($payload["userId"]); }
Il est impossible pour un attaquant de générer un jeton valide sans avoir accès au secret du serveur. Cela signifie également que la perte accidentelle – ou la rotation délibérée – du secret invalidera immédiatement tous les jetons précédemment émis.
Dans une situation réelle, votre code d’authentification doit également inspecter l’expiration et les horodatages « pas avant » dans la charge utile du jeton. Ceux-ci sont utilisés pour déterminer si la session de l’utilisateur est toujours valide.
Quand utiliser les JWT
Les JWT sont fréquemment utilisés pour l’authentification API car ils sont simples à mettre en œuvre sur le serveur, faciles à utiliser sur le client et simples à transmettre au-delà des limites du réseau. Malgré leur simplicité, ils ont une bonne sécurité car chaque jeton est signé à l’aide de la clé secrète du serveur.
Les JWT sont un mécanisme sans état, vous n’avez donc pas besoin d’enregistrer des informations sur les jetons émis sur votre serveur. Vous pouvez obtenir des informations sur le client qui présente un JWT à partir de la charge utile du jeton, au lieu d’avoir à effectuer une recherche dans une base de données. Ces informations peuvent être fiables en toute sécurité une fois que vous avez vérifié la signature du jeton.
L’utilisation de JWT est un bon choix chaque fois que vous avez besoin d’échanger des informations entre deux parties sans risque de falsification. Il y a cependant des points faibles dont il faut être conscient : l’ensemble du système sera compromis si la clé secrète de votre serveur est divulguée ou si votre code de vérification de signature contient un bogue. Pour cette raison, de nombreux développeurs choisissent d’utiliser une bibliothèque open source pour implémenter la génération et la validation JWT. Des options sont disponibles pour tous les langages de programmation courants. Ils éliminent le risque d’oubli lorsque vous vérifiez vous-même les jetons.
Sommaire
La norme JWT est un format d’échange de données qui inclut une vérification d’intégrité intégrée. Les JWT sont couramment utilisés pour sécuriser les interactions entre les serveurs d’API et les applications clientes. Le serveur peut faire confiance aux jetons entrants s’il est capable de reproduire leurs signatures. Cela permet d’effectuer des actions en toute sécurité à l’aide des informations obtenues à partir de la charge utile du jeton.
Les JWT sont pratiques mais ils présentent certains inconvénients. La représentation textuelle encodée en Base64 d’un JWT peut rapidement devenir volumineuse si vous avez plus d’une poignée de champs de charge utile. Cela pourrait devenir une surcharge inacceptable lorsque votre client doit envoyer le JWT avec chaque demande.
L’apatridie des JWT est également un autre inconvénient potentiel : une fois émis, les jetons sont immuables et doivent être utilisés tels quels jusqu’à leur expiration. Les clients qui utilisent des charges utiles JWT pour déterminer les autorisations ou les fonctionnalités sous licence d’un utilisateur devront obtenir un nouveau jeton du backend chaque fois que leurs attributions changent.