4 points par GN⁺ 18 시간 전 | 2 commentaires | Partager sur WhatsApp
  • Les JWT ne sont pas adaptés au maintien de la connexion d’un utilisateur ; pour cet usage, une session par cookie classique est plus appropriée
  • La spécification JWT part du principe de jetons à courte durée de vie, d’environ 5 minutes ou moins, alors qu’une session nécessite une durée de vie plus longue
  • Une authentification sans état sûre est difficile à mettre en œuvre, et pour manipuler les jetons en toute sécurité, il faut finalement une certaine forme de stockage d’état
  • Un JWT qui ne contient qu’un simple jeton de session est moins efficace et moins flexible qu’un cookie de session classique, et les informations d’authentification ne doivent pas être stockées dans localStorage ou sessionStorage
  • Lorsqu’un jeton signé à courte durée de vie est nécessaire, PASETO, conçu pour la sécurité, est un meilleur choix, mais il ne doit pas être utilisé pour les sessions

Résumé essentiel

  • Les JWT ne doivent pas être utilisés pour maintenir un utilisateur connecté ; pour cela, une session par cookie classique est un meilleur outil
  • Les JWT n’ont pas été conçus pour cet usage et ne sont pas sûrs ; pour maintenir une session de connexion, une session par cookie standard est plus adaptée
  • Sujet connexe : les identifiants d’authentification, y compris les jetons JWT, ne doivent pas être stockés dans localStorage ou sessionStorage
  • On peut consulter des présentations sur les JWT, mais d’autres sujets comme la protection CSRF n’y sont généralement abordés que brièvement ; il faut donc les apprendre séparément auprès d’autres sources
  • Même les cas d’usage « valides » des JWT évoqués à la fin de la vidéo peuvent être traités facilement avec des outils meilleurs et plus sûrs, en particulier PASETO

Pourquoi il faut éviter les JWT

  • La spécification JWT a été conçue uniquement pour des jetons à très courte durée de vie, d’environ 5 minutes ou moins, tandis qu’une session a besoin d’une durée de vie plus longue
  • Une authentification sans état sûre n’est pas possible, et pour traiter les jetons de manière sécurisée, un certain état est nécessaire
    • Si un stockage de données est nécessaire, il vaut mieux stocker toutes les données plutôt que de ne gérer qu’une partie de l’état des jetons
    • Le sujet est traité plus en détail sur http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
    • Il existe en pratique des applications qui utilisent les JWT de cette manière, mais ces applications sont défectueuses ; il ne faut donc pas répéter la même erreur
  • Un JWT qui ne stocke qu’un simple jeton de session est moins efficace et moins flexible qu’un cookie de session classique, sans apporter d’avantage supplémentaire
  • La spécification JWT elle-même n’inspire pas confiance aux experts en sécurité ; il faut donc l’écarter pour l’ensemble des usages liés à la sécurité et à l’authentification

Réponses aux objections

  • L’objection « Google utilise aussi des JWT » ne concerne pas les sessions utilisateur dans le navigateur
    • Google n’utilise pas de JWT pour les sessions utilisateur dans le navigateur, mais des sessions par cookie classiques
    • Les JWT ne servent que de mécanisme de transport Single Sign On pour transmettre une session de connexion d’un serveur ou d’un hôte à un autre
    • Cet usage entre dans le cadre des cas d’usage raisonnables des JWT
    • Google dispose des ressources en experts sécurité nécessaires pour créer et maintenir une implémentation JWT plus sûre
    • Les JWT de Google ne sont, en pratique, pas les mêmes que ceux utilisés ailleurs
  • L’objection « sans état, c’est mieux » n’est pas compatible avec les exigences d’une authentification sûre
    • Sans ressources massives, il n’est pas possible d’exploiter en toute sécurité une authentification véritablement sans état
    • Pour approfondir, on peut consulter Stateless is a lie
  • Le problème « je ne sais pas comment configurer des sessions » se résout, dans la plupart des cas, avec la documentation et les implémentations des frameworks de serveurs web
    • La technologie des sessions n’est pas particulièrement nouvelle, c’est pourquoi on voit rarement des articles qui l’expliquent
    • La documentation de l’implémentation de session devrait suffire à suivre la procédure de configuration
    • Presque tous les frameworks de serveurs web intègrent une implémentation de session et, même si elle n’est pas activée par défaut, il est généralement facile de l’activer
    • Express et d’autres frameworks Node.js constituent dans une certaine mesure une exception, en raison de leur forte modularité et de leur caractère très spécialisé
    • Avec Express, il suffit d’utiliser le middleware express-session et un connecteur de store adapté au stockage choisi
    • Il est recommandé d’utiliser connect-session-knex avec Postgres, MySQL ou, si possible, SQLite

Jetons à court terme

  • Si vous avez besoin de jetons signés à courte durée de vie pour un usage donné, il existe une meilleure spécification, conçue pour la sécurité : PASETO
  • Même avec PASETO, il ne faut pas l’utiliser pour des sessions

Fonctionnement des sessions

  • Pour mieux comprendre le fonctionnement des sessions, il vaut mieux consulter le gist de joepie91

2 commentaires

 
shj5508 9 시간 전
  1. Le JWT est une méthode visant à chiffrer le token et à réduire les requêtes vers la base de données ; ce n’est pas un concept opposé à l’authentification par cookie. Si le JWT est stocké dans un cookie sécurisé, le risque de vol est le même qu’avec la méthode d’authentification par cookie héritée.

  2. Le fait de gérer une liste d’expiration pour faire expirer les JWT présente un avantage en termes de performances. Il existe une différence de coût entre interroger Redis uniquement pour les informations d’expiration et interroger la base de données pour l’ensemble des utilisateurs.

Des dizaines de milliers de requêtes avec une interrogation indexée sur 100000 lignes membres (méthode de cookie héritée)
vs
Des dizaines de milliers de requêtes avec 50 consultations de la liste d’expiration dans Redis (méthode d’expiration immédiate des JWT)

Le JWT a donc bien des avantages. C’est simplement moins visible dans un environnement de petite taille.

 
Commentaires sur Hacker News
  • Il manque un indice essentiel : on parle ici des sessions utilisateur basées sur le navigateur
    Pour la communication entre services, il y a souvent de nombreux cas où JWT convient très bien
    Cela dit, j’ai lu une partie de l’article lié, et il y a par exemple un texte comme https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba.... Si JWT était vraiment une norme de sécurité aussi catastrophique, il suffirait de publier une méthode pour compromettre AssumeRoleWithWebIdentity d’AWS STS, ou bien de ne rien publier et de lancer des mineurs de cryptomonnaie sur les comptes AWS de production de chaque entreprise du Fortune 500. Si JWT est vraiment si peu sûr, merci de nous prévenir quand vous aurez réussi /sarcasme

    • C’est exactement la conclusion raisonnable. Je suis d’accord sur le fait que JWT est le mauvais outil pour les sessions utilisateur dans le navigateur
      Les parties signature/chiffrement de JWT sont complexes, et les bibliothèques JWT courantes ne se sont mises au niveau que récemment ; auparavant, ce n’était pas le cas. Beaucoup acceptaient l’algorithme "none" [1], et dans certains cas des clés publiques étaient utilisées comme si c’étaient des secrets partagés, permettant à un attaquant de forger des jetons [2]. C’est précisément le résultat de la complexité que critique l’article lié
      JWT peut aussi ne pas fournir les fonctionnalités attendues pour les sessions utilisateur. On ne peut pas l’invalider sans maintenir quelque part une liste de révocation. Mais s’il faut de toute façon comparer un identifiant à une liste de révocation à chaque requête, autant utiliser un ID de session opaque et faire une recherche à chaque requête. Bien sûr, on peut utiliser des jetons à courte durée de vie et les renouveler en permanence, mais dans une application classique qui conserve déjà de l’état, il y a peu de raisons de le faire
      En revanche, je suis totalement d’accord sur le fait que les jetons signés peuvent être utiles dans les systèmes distribués ou les communications machine à machine. Il ne faut pas confondre les deux cas
      [1] https://nvd.nist.gov/vuln/detail/cve-2022-23540
      [2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150
    • Les JWT d’autrefois posaient surtout problème à cause de bibliothèques aux mauvais réglages par défaut. Il y a quelques années, les attaques par rétrogradation étaient aussi assez courantes
      Aujourd’hui, les principales bibliothèques dans plusieurs langages ont adopté des valeurs par défaut plus saines, donc dans la pratique c’est devenu plutôt sûr
    • JOSE peut encore poser problème même s’il est sûr quand il est correctement implémenté. La surface des API associées laisse souvent à désirer
      Si “c’est sûr à condition de bien le prendre en main et de bien s’en servir” suffisait à qualifier une conception de bonne, on pourrait dire la même chose de X.509
      Dans bien des cas, il existe de meilleures alternatives. Les jetons de session standard ou les clés API sont largement utilisés sur la plupart des grands sites web, et conviennent presque parfaitement à la majorité des usages
      Il ne s’agit pas de dire que ces standards n’ont aucune valeur. Leur principal intérêt est de fournir un standard de base pour échanger des données sans passer par quelque chose comme l’encodage ASN.1, dont les outils semblent très fragiles et bourrés de bugs
    • Je suis d’accord avec la première partie, mais l’ajout est un sophisme. Ce n’est pas parce qu’on ne sait pas forcément exploiter quelque chose qu’on ne peut pas dire que ce n’est pas sûr
      Par exemple, je ne sais pas comment exploiter SAML, mais je sais que c’est un standard affreux parce qu’il transforme l’ensemble du parseur XML en surface d’attaque. Je ne suis pas chercheur en sécurité et je ne sais pas trouver des vulnérabilités dans un parseur XML, mais je peux quand même comprendre qu’une grande surface d’attaque, c’est mauvais
    • La lignée de vulnérabilités liées à JWT dans des applications réelles est longue
  • Dire que JWT n’est pas sûr, cela vaut aussi si on utilise une signature RSA / à clé publique de confiance ? Même sans secret partagé ?
    L’argument selon lequel les JWT vivraient trop longtemps me semble aussi étrange. Il suffit de limiter leur durée de vie et de mettre en place un modèle de renouvellement vis-à-vis de l’autorité d’authentification. Même avec des sessions basées sur des cookies, il faut bien stocker quelque chose quelque part au final. On peut rendre un JWT valable seulement 5 à 15 minutes, et 15 minutes correspond à peu près à la durée de cache de plusieurs systèmes d’autorisation, dont Entra. Même des jetons de 5 minutes restent tout à fait utilisables dans le navigateur s’il existe un système de renouvellement
    Enfin, je préfère séparer l’identité/l’authentification des services applicatifs et API. Cela permet d’externaliser le contexte, et traiter un JWT à chaque requête est plus simple à gérer qu’un système partagé de cache/état susceptible d’échouer de façon intermittente. Les jetons signés permettent de vérifier la signature vis-à-vis d’une autorité connue

    • Un JWT peut être configuré pour devenir invalide dans 30 secondes, voire dans 1 seconde. Lorsqu’on crée un JWT, il faut définir une audience
      En dehors de cela, la signature est cryptographiquement valide. Il suffit de vérifier chaque JWT à chaque fois avec une courte durée de vie
      À noter que les jetons OIDC sont tous des JWT
  • Si l’on compare les sessions et les listes de révocation JWT, il existe aussi une logique en faveur des listes de révocation JWT. Comme les JWT ont une date d’expiration limitée, il suffit de conserver la liste de révocation pour les jetons pas encore expirés
    Il est probable que les JWT révoqués ne représentent qu’une fraction des JWT valides en circulation ; on ne consulte donc qu’un très petit jeu de données à chaque requête
    Avec des sessions, la liste des sessions valides a de fortes chances d’être de plusieurs ordres de grandeur plus grande que la liste de révocation, ce qui augmente le coût en lecture et en stockage lié à l’état
    En outre, l’article affirme que JWT est sans état, mais en général ce n’est pas le cas. En pratique, on ne se contente généralement pas de valider le JWT : on récupère aussi à chaque requête l’objet d’identité correspondant, c’est-à-dire les détails de l’utilisateur, pour vérifier qu’il est toujours actif et autorisé à effectuer l’action concernée. On peut utiliser une liste de révocation par utilisateur ou une valeur comme minimum_issued_at pour valider le champ iat du JWT. Cela permet aussi un modèle de “déconnexion sur tous les appareils” : il suffit que cette action définisse minimum_issued_at de l’utilisateur à $NOW, et tous les jetons antérieurs sont alors révoqués. Plus besoin de consulter une liste de révocation individuelle

    • À partir du moment où il faut charger l’objet utilisateur, le principal avantage de JWT a disparu, donc autant l’abandonner
    • Une lecture de données de session, c’est juste un select indexé dans la base de données, qui renvoie 0 ou 1 ligne. Dans la plupart des cas, il n’y a pas lieu de s’en inquiéter
    • Les sessions aussi ont une date d’expiration, qu’on peut régler comme on veut
  • Cet article renvoie, pour l’essentiel du « pourquoi », à d’autres billets de blog, et ceux-ci semblent surtout se plaindre du fait qu’« on ne peut pas invalider individuellement des jetons JWT »
    Chaque fois que j’ai dû l’implémenter, la recommandation générale a toujours été de vérifier quelque part les nonce invalidés, ce qui répond aussi au deuxième argument de cet article
    L’affirmation selon laquelle « la spécification JWT elle-même n’est pas digne de confiance aux yeux des experts en sécurité » semble demander plus de preuves qu’un seul billet de blog. Et cet article a surtout l’air d’accuser de mauvaises implémentations, alors que ce problème existe avec n’importe quel standard
    Globalement, je ne sais pas vraiment à quoi je m’attendais en cliquant sur un lien vers un gist au hasard

    • Certaines implémentations initiales permettaient de définir n’importe quelle autorité dans l’en-tête et de lui faire confiance telle quelle, ce qui était évidemment une mauvaise approche dès le départ. Si l’on n’autorise que des autorités de confiance ou « connues », beaucoup de préoccupations contextuelles disparaissent
      Par ailleurs, on peut tout à fait utiliser dans le navigateur des JWT à durée de vie plus courte et faire en sorte que l’agent les renouvelle lui-même. Avec Azure Entra ou plusieurs autres fournisseurs, c’est effectivement ainsi que cela fonctionne. On peut garder des JWT relativement courts, de l’ordre de 5 à 15 minutes, et même vérifier si le jti a été révoqué
      Les JWT sont très utiles pour séparer et réutiliser l’autorité d’accès du système applicatif/API. On déplace la surface d’attaque, mais on la déplace d’une manière fiable. Le monde entier utilise des mécanismes à clé publique à de nombreux endroits, y compris pour SSH. Je n’utiliserais ni secret partagé ni jeton longue durée, mais des jetons signés à clé publique, de courte durée et provenant d’une source vérifiée et connue, sont en général tout à fait acceptables
      En pratique, ce sont plus souvent les clés API qui posent problème. J’ai justement dû en implémenter récemment ; dans mon cas, j’ai fait en sorte que les clés API ressemblent aussi à des jetons Bearer, avec un court préfixe sak., suivi d’une partie identité (octets UUID en base64url), puis d’une valeur secrète (octets en base64url). Dans la base de données, je stocke l’UUID et un salt+hash de niveau phrase de passe dérivé de la valeur secrète. Ainsi, la clé API générée doit être traitée comme un secret, et elle n’est stockée en base qu’à sens unique, de sorte qu’une compromission de la base n’entraîne pas automatiquement une compromission de l’authentification
      Malgré tout, une fuite de clé API reste bien plus probable qu’un problème sur une solution JWT correctement implémentée
    • Je ne suis pas du tout d’accord à 100 % avec l’idée qu’« on ne peut pas invalider individuellement des jetons JWT ». Lors de l’implémentation, vérifier quelque part les nonce invalidés est pour moi du simple bon sens, et je suis toujours surpris de redécouvrir que beaucoup de gens ne le font pas
  • Je suis tombé sur cet article par hasard, et comme j’avais beaucoup travaillé sur ce sujet autrefois, j’ai trouvé intéressant qu’il revienne dans l’actualité maintenant. Puis j’ai cliqué et j’ai vu que l’auteur renvoyait vers certains de mes documents. Ça m’a vraiment rappelé de vieux souvenirs
    Quoi qu’il en soit, des gens bien plus intelligents que moi ont traité ce sujet très largement pendant des années, mais même en 2026, je pense toujours que les JWT sont le mauvais outil pour l’authentification web. Pour du service à service, ça va, mais si l’on a le choix, mieux vaut simplement utiliser PASETO. Ça résout beaucoup de problèmes

  • Je suis en train d’ajouter RabbitMQ à un site pour les notifications push. J’utilise une authentification JWT pour contrôler ce que le client peut lire et où, avec une courte durée de vie et un renouvellement périodique des jetons
    Je ne connais pas vraiment d’autre configuration qui arrive à ce niveau de simplicité. Il suffit d’ajouter un endpoint qui fournit un jeton JWT à une session valide, et c’est réglé, avec en plus la possibilité d’avoir des autorisations par utilisateur

  • L’un des articles liés, censé expliquer pourquoi il ne faut pas utiliser JWT, est au mieux étrange
    https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
    En résumé, il dit que « certaines bibliothèques avaient des bugs », puis recommande d’utiliser libsodium et de tout faire soi-même. C’est un conseil absurde qu’il est difficile de prendre au sérieux. Tous les logiciels ont des bugs. Avec Heartbleed, tout Internet a paniqué, et pourtant nous utilisons toujours TLS et OpenSSL
    J’entends pour la première fois l’affirmation selon laquelle « la spécification JWT a été conçue spécifiquement pour des jetons à très courte durée de vie, environ 5 minutes ou moins », et je ne trouve rien pour l’étayer. La RFC 7519 ne contient pas ce genre d’affirmation

  • En général, on utilise les JWT comme un cache d’authentification. On reçoit un jeton d’authentification d’un service d’authentification, et ce jeton accorde ensuite des droits sur d’autres services
    Cela présente plusieurs avantages, mais l’essentiel est que les services en aval n’ont pas besoin d’interagir avec la base de données d’authentification ni d’avoir le droit d’émettre des jetons. En partant du principe qu’on utilise RS256 et non HMAC. Ainsi, même si un service en aval est compromis, ce n’est pas aussi critique que si l’était un service ayant accès à la base d’authentification
    Si le jeton contient des données sensibles, il faut utiliser JWE, mais ce n’est pas idéal puisqu’il faut demander à un service interne possédant la clé privée de déchiffrer le jeton à chaque utilisation
    La structure que j’utilise souvent est {"id": (uuid), "scopes": ["scope:read/write"]}
    C’est aussi assez bien adapté aux SPA. Le serveur de site statique peut vérifier le JWE avec une clé publique avant de servir les ressources. Ma méthode consiste à compiler le site statique sous la forme /(scope)/path, afin que le service statique ne serve tout simplement pas, dès le départ, les pages auxquelles il n’est pas possible d’accéder. C’est très utile quand on ne veut pas exposer à l’utilisateur des fonctionnalités détenues par le back-end, comme un panneau d’administration, ou des chemins de services internes susceptibles d’être attaqués
    La durée de vie du JWT pour l’« accès back-end » est d’environ 5 minutes, et les éléments comme /me sont mis en cache dans localStorage, sauf si /refresh indique explicitement de vider ce cache localStorage. Le gestionnaire de requêtes de l’application SPA détecte qu’un « renouvellement est nécessaire » et renouvelle alors le jeton
    Je pense qu’une grande partie de cette responsabilité repose sur les bibliothèques node/next et Python. J’écris le back-end dans des langages à typage fort, et le front-end est toujours constitué de pages statiques précompilées. Actuellement, ma configuration front-end utilise VITE, avec des pages de destination pré-rendues et l’application en SPA classique
    Même en tenant compte de tout cela, je suis en profond désaccord avec l’ensemble de ce gist. On peut rendre JWT aussi sûr qu’on le souhaite

  • Les JWT sont corrects, et le titre me paraît un peu putaclic
    En revanche, il y a de vrais sujets intéressants à aborder : quand utiliser des valeurs chiffrées (symétriques ou asymétriques), des valeurs aléatoires mais secrètes, des valeurs signées (lisibles mais non falsifiables), où stocker ces valeurs (mémoire, localStorage, cookies), comment faire en sorte qu’elles ne durent pas éternellement et s’il faut pouvoir les révoquer avant leur expiration naturelle