Bonnes pratiques pour la gestion des jetons JWT côté serveur [fermé]


111

(engendré à partir de ce fil car c'est vraiment une question en soi et non spécifique à NodeJS, etc.)

J'implémente un serveur API REST avec authentification et j'ai implémenté avec succès la gestion des jetons JWT afin qu'un utilisateur puisse se connecter via un point de terminaison / login avec un nom d'utilisateur / mot de passe, sur lequel un jeton JWT est généré à partir d'un secret de serveur et renvoyé au client. Le jeton est ensuite passé du client au serveur dans chaque demande d'API authentifiée, sur laquelle le secret du serveur est utilisé pour vérifier le jeton.

Cependant, j'essaie de comprendre les meilleures pratiques pour savoir exactement comment et dans quelle mesure le jeton doit être validé, pour créer un système vraiment sécurisé. Que faut-il impliquer exactement dans la «validation» du jeton? Est-il suffisant que la signature puisse être vérifiée à l'aide du secret du serveur, ou dois-je également contre-vérifier le jeton et / ou la charge utile du jeton par rapport à certaines données stockées sur le serveur?

Un système d'authentification basé sur des jetons ne sera aussi sûr que de transmettre un nom d'utilisateur / mot de passe dans chaque demande à condition qu'il soit tout aussi ou plus difficile d'obtenir un jeton que d'obtenir le mot de passe d'un utilisateur. Cependant, dans les exemples que j'ai vus, les seules informations requises pour produire un jeton sont le nom d'utilisateur et le secret côté serveur. Cela ne signifie-t-il pas qu'en supposant pendant une minute qu'un utilisateur malveillant acquiert la connaissance du secret du serveur, il peut maintenant produire des jetons au nom de n'importe quel utilisateur, ayant ainsi accès non seulement à un utilisateur donné, comme le serait le fait si un mot de passe était obtenu, mais en fait à tous les comptes d'utilisateurs?

Cela m'amène aux questions:

1) La validation du jeton JWT devrait-elle être limitée à la vérification de la signature du jeton lui-même, en s'appuyant uniquement sur l'intégrité du secret du serveur, ou accompagnée d'un mécanisme de validation distinct?

  • Dans certains cas, j'ai vu l'utilisation combinée de jetons et de sessions serveur où, lors d'une connexion réussie via le point de terminaison / login, une session est établie. Les demandes d'API valident le jeton et comparent également les données décodées trouvées dans le jeton avec certaines données stockées dans la session. Cependant, utiliser des sessions signifie utiliser des cookies et, dans un certain sens, cela va à l'encontre de l'objectif d'utiliser une approche basée sur des jetons. Cela peut également causer des problèmes à certains clients.

  • On pourrait imaginer que le serveur garde tous les jetons actuellement utilisés dans un cache mémoire ou similaire, pour s'assurer que même si le secret du serveur est compromis de sorte qu'un attaquant puisse produire des jetons «valides», seuls les jetons exacts qui ont été générés via le point de terminaison / login serait accepté. Est-ce raisonnable ou simplement redondant / exagéré?

2) Si la vérification de la signature JWT est le seul moyen de valider les jetons, ce qui signifie que l'intégrité du secret du serveur est le point de rupture, comment les secrets du serveur doivent-ils être gérés? Lire à partir d'une variable d'environnement et créé (aléatoire?) Une fois par pile déployée? Renouvelé ou pivoté périodiquement (et si tel est le cas, comment gérer les jetons valides existants qui ont été créés avant la rotation mais qui doivent être validés après la rotation, peut-être suffit-il si le serveur conserve le secret actuel et le secret précédent à un moment donné) ? Autre chose?

Peut-être que je suis simplement trop paranoïaque en ce qui concerne le risque de compromission du secret du serveur, ce qui est bien sûr un problème plus général qui doit être résolu dans toutes les situations cryptographiques ...


1
Il y a de grandes questions. Re: question 2. J'ai le même problème avec TOUTES les clés secrètes conservées côté serveur. Si vous effectuez une sorte de correspondance de hachage ou de décryptage asymétrique, qu'il s'agisse de signer un jwt ou de décrypter les informations cc stockées dans la base de données, vous devez avoir une clé secrète accessible par code sur le serveur. Alors, où diable le gardez-vous ?? Voici la meilleure réponse que j'ai trouvée: pcinetwork.org/forum/index.php?threads / ... - probablement aussi sécurisé que pour une clé jwt aussi.
jbd

Quelle est la clé secrète dans le jeton jwt? Je pense que jwt token lui-même est un secret. Ou une clé secrète pourrait être RSAPrivateKey privateKey??
kittu le

3
Cela a été demandé il y a quelque temps, mais peut-être que quelqu'un le trouvera utile. Dans mon cas, j'ai une "clé secrète" par utilisateur. Ainsi, chaque fois qu'un utilisateur se connecte, je génère ce secret et le stocke avec l'enregistrement utilisateur dans la base de données. Je valide le jeton en utilisant ce secret. Lors de la déconnexion, j'efface cette valeur. Cela invalide automatiquement les autres jetons créés auparavant (c'est ce dont j'avais besoin).
Nelson Rodriguez

Réponses:


52

J'ai également joué avec des jetons pour mon application. Bien que je ne sois en aucun cas un expert, je peux partager certaines de mes expériences et réflexions sur la question.

Le but des JWT est essentiellement l'intégrité. Il fournit un mécanisme permettant à votre serveur de vérifier que le jeton qui lui a été fourni est authentique et a été fourni par votre serveur. La signature générée via votre secret est ce qui prévoit cela. Donc, oui, si votre secret est divulgué d'une manière ou d'une autre, cet individu peut générer des jetons que votre serveur penserait être les siens. Un système basé sur des jetons serait toujours plus sécurisé que votre système de nom d'utilisateur / mot de passe simplement en raison de la vérification de la signature. Et dans ce cas, si quelqu'un a de toute façon votre secret, votre système a d'autres problèmes de sécurité à résoudre que quelqu'un qui fabrique de faux jetons (et même dans ce cas, le simple fait de changer le secret garantit que tous les jetons créés avec l'ancien secret sont désormais invalides).

En ce qui concerne la charge utile, la signature vous indiquera seulement que le jeton qui vous a été fourni était exactement tel qu'il était lorsque votre serveur l'a envoyé. vérifier que le contenu des charges utiles est valide ou approprié pour votre application est évidemment à vous.

Pour vos questions:

1.) D'après mon expérience limitée, il est certainement préférable de vérifier vos jetons avec un deuxième système. Valider simplement la signature signifie simplement que le jeton a été généré avec votre secret. Le stockage des jetons créés dans une sorte de base de données (redis, memcache / sql / mongo ou un autre stockage) est un moyen fantastique de vous assurer que vous n'acceptez que les jetons créés par votre serveur. Dans ce scénario, même si votre secret est divulgué, cela n'aura pas trop d'importance car les jetons générés ne seront pas valides de toute façon. C'est l'approche que j'adopte avec mon système - tous les jetons générés sont stockés dans une base de données (redis) et à chaque demande, je vérifie que le jeton est dans ma base de données avant de l'accepter. De cette façon, les jetons peuvent être révoqués pour n'importe quelle raison, comme les jetons qui ont été libérés dans la nature, la déconnexion de l'utilisateur, les changements de mot de passe, les changements de secret, etc.

2.) C'est quelque chose dans lequel je n'ai pas beaucoup d'expérience et c'est quelque chose que je recherche toujours activement car je ne suis pas un professionnel de la sécurité. Si vous trouvez des ressources, n'hésitez pas à les publier ici! Actuellement, j'utilise simplement une clé privée que je charge à partir du disque, mais c'est évidemment loin d'être la solution la meilleure ou la plus sécurisée.


5
Pour le deuxième point, voici une bonne réponse: security.stackexchange.com/questions/87130/…
Bossliaw

1
Comme les jetons sont disponibles dans l'en-tête, que se passe-t-il si le jeton est volé et qu'un malveillant tente de se connecter avec ce jeton (en connaissant l'adresse e-mail de l'utilisateur)?
kittu

22
Si vous stockez chaque JWT, alors il n'y a aucun avantage à JWT et vous pouvez aussi bien vous en tenir aux identifiants de session aléatoires.
ColinM du

46

Voici quelques éléments à prendre en compte lors de la mise en œuvre de JWT dans votre application:

  • Gardez votre durée de vie JWT relativement courte et faites-en gérer la durée de vie sur le serveur. Si vous ne le faites pas et que vous avez besoin ultérieurement de plus d'informations dans vos JWT, vous devrez soit prendre en charge 2 versions, soit attendre que vos anciens JWT aient expiré avant de pouvoir implémenter votre modification. Vous pouvez facilement le gérer sur le serveur si vous ne regardez que le iatchamp dans le jwt et ignorez le expchamp.

  • Pensez à inclure l'url de la requête dans votre JWT. Par exemple, si vous souhaitez que votre JWT soit utilisé au point de terminaison /my/test/path, incluez un champ comme 'url':'/my/test/path'dans votre JWT, pour vous assurer qu'il n'est jamais utilisé que sur ce chemin. Si vous ne le faites pas, vous constaterez peut-être que les utilisateurs commencent à utiliser vos JWT à d'autres points de terminaison, même ceux pour lesquels ils n'ont pas été créés. Vous pouvez également envisager d'inclure un md5 (url) à la place, car avoir une grande URL dans le JWT finira par rendre le JWT beaucoup plus grand, et ils peuvent devenir assez gros.

  • L'expiration de JWT doit être configurable par chaque cas d'utilisation si les JWT sont mis en œuvre dans une API. Par exemple, si vous avez 10 points de terminaison pour 10 cas d'utilisation différents pour les JWT, assurez-vous que vous pouvez faire en sorte que chaque point de terminaison accepte les JWT qui expirent à des moments différents. Cela vous permet de verrouiller certains points de terminaison plus que d'autres, si, par exemple, les données servies par un point de terminaison sont très sensibles.

  • Au lieu d'expirer simplement les JWT après un certain temps, envisagez d'implémenter des JWT qui prennent en charge les deux:

    • N utilisations - ne peuvent être utilisées que N fois avant leur expiration et
    • expirer après un certain temps (si vous avez un jeton à usage unique, vous ne voulez pas qu'il vive éternellement s'il n'est pas utilisé, n'est-ce pas?)
  • Tous les échecs d'authentification JWT doivent générer un en-tête de réponse "erreur" indiquant pourquoi l'authentification JWT a échoué. par exemple "expiré", "aucune utilisation laissée", "révoqué", etc. Cela aide les implémenteurs à savoir pourquoi leur JWT échoue.

  • Pensez à ignorer l '«en-tête» de vos JWT car ils fuient des informations et donnent une mesure de contrôle aux pirates. Ceci concerne principalement le algchamp dans l'en-tête - ignorez cela et supposez simplement que l'en-tête est ce que vous voulez prendre en charge, car cela évite aux pirates d'essayer d'utiliser l' Nonealgorithme, ce qui supprime le contrôle de sécurité de la signature.

  • Les JWT doivent inclure un identifiant détaillant quelle application a généré le jeton. Par exemple, si vos JWT sont créés par 2 clients différents, mychat et myclassifiedsapp, chacun doit inclure son nom de projet ou quelque chose de similaire dans le champ "iss" du JWT, par exemple "iss": "mychat"

  • Les JWT ne doivent pas être enregistrés dans les fichiers journaux. Le contenu d'un JWT peut être enregistré, mais pas le JWT lui-même. Cela garantit que les développeurs ou autres ne peuvent pas récupérer les JWT des fichiers journaux et faire des choses sur les comptes d'autres utilisateurs.
  • Assurez-vous que votre implémentation JWT n'autorise pas l'algorithme "Aucun", pour éviter que les pirates informatiques créent des jetons sans les signer. Cette classe d'erreurs peut être entièrement évitée en ignorant "l'en-tête" de votre JWT.
  • Pensez fortement à utiliser iat(émis à) au lieu de exp(expiration) dans vos JWT. Pourquoi? Puisque cela iatsignifie essentiellement quand le JWT a été créé, cela vous permet d'ajuster sur le serveur quand le JWT expire, en fonction de la date de création. Si quelqu'un décède dans exp20 ans dans le futur, le JWT vit pour toujours! Notez que vous expirez automatiquement les JWT si leur iatest dans le futur, mais laissez un peu de marge de manœuvre (par exemple 10 secondes), au cas où l'heure du client serait légèrement désynchronisée avec l'heure du serveur.
  • Envisagez d'implémenter un point de terminaison pour créer des JWT à partir d'une charge utile json et forcez tous vos clients d'implémentation à utiliser ce point de terminaison pour créer leurs JWT. Cela garantit que vous pouvez résoudre tous les problèmes de sécurité que vous souhaitez avec la façon dont les JWT sont créés en un seul endroit, facilement. Nous ne l'avons pas fait tout de suite dans notre application et devons maintenant diffuser lentement les mises à jour de sécurité côté serveur JWT, car nos 5 clients différents ont besoin de temps pour les implémenter. De plus, faites en sorte que votre point de terminaison de création accepte un tableau de charges utiles json que les JWT doivent créer, ce qui réduira le nombre de requêtes http entrant à ce point de terminaison pour vos clients.
  • Si vos JWT sont utilisés à des points de terminaison qui prennent également en charge l'utilisation par session, assurez-vous de ne rien mettre dans votre JWT qui soit nécessaire pour satisfaire la demande. Vous pouvez facilement le faire si vous vous assurez que votre point de terminaison fonctionne avec une session, lorsqu'aucun JWT n'est fourni.
  • Ainsi, les JWT finissent généralement par contenir un userId ou un groupId, et permettent d'accéder à une partie de votre système en fonction de ces informations. Assurez-vous de ne pas autoriser les utilisateurs d'une zone de votre application à se faire passer pour d'autres utilisateurs, en particulier si cela permet d'accéder à des données sensibles. Pourquoi? Eh bien, même si votre processus de génération de JWT n'est accessible qu'aux services «internes», les développeurs ou d'autres équipes internes pourraient générer des JWT pour accéder aux données de n'importe quel utilisateur, par exemple le PDG de l'entreprise d'un client aléatoire. Par exemple, si votre application donne accès aux dossiers financiers pour les clients, en générant un JWT, un développeur peut saisir les dossiers financiers de n'importe quelle entreprise! Et si un pirate informatique pénètre de toute façon dans votre réseau interne, il peut faire de même.
  • Si vous prévoyez d'autoriser la mise en cache d'une URL contenant un JWT de quelque manière que ce soit, assurez-vous que les autorisations des différents utilisateurs sont incluses dans l'URL et non dans le JWT. Pourquoi? Parce que les utilisateurs peuvent finir par obtenir des données, ils ne devraient pas. Par exemple, supposons qu'un super utilisateur se connecte à votre application et demande l'url suivante:, /mysite/userInfo?jwt=XXXet que cette URL soit mise en cache. Ils se déconnectent et quelques minutes plus tard, un utilisateur régulier se connecte à votre application. Ils obtiendront le contenu mis en cache - avec des informations sur un super utilisateur! Cela a tendance à se produire moins sur le client et plus sur le serveur, en particulier dans les cas où vous utilisez un CDN comme Akamai et que vous laissez certains fichiers vivre plus longtemps. Cela peut être résolu en incluant les informations utilisateur pertinentes dans l'URL et en la validant sur le serveur, même pour les demandes en cache, par exemple/mysite/userInfo?id=52&jwt=XXX
  • Si votre jwt est destiné à être utilisé comme un cookie de session, et ne doit fonctionner que sur la même machine pour laquelle jwt a été créé, vous devriez envisager d'ajouter un champ jti à votre jwt. Il s'agit essentiellement d'un jeton CSRF, qui garantit que votre JWT ne peut pas être transmis du navigateur d'un utilisateur à un autre.

1
Ce que vous appelez created_by, il y a déjà une réclamation pour cela dans JWT et il est appelé iss(émetteur).
Fred

ouais bon point - je vais mettre à jour avec ça ... merci!
Brad Parks

8

Je ne pense pas être un expert mais j'aimerais partager quelques réflexions sur Jwt.

  • 1: Comme l'a dit Akshay, il est préférable d'avoir un deuxième système pour valider votre jeton.

    a: La façon dont je le gère: je stocke le hachage généré dans un stockage de session avec l'heure d'expiration. Pour valider un jeton, il doit avoir été émis par le serveur.

    b.:Il y a au moins une chose qui doit être vérifiée la méthode de signature utilisée. par exemple :

    header :
    {
      "alg": "none",
      "typ": "JWT"
    }
    

Certaines bibliothèques validant JWT accepteraient celui-ci sans vérifier le hachage. Cela signifie que sans connaître votre sel utilisé pour signer le jeton, un pirate informatique pourrait s'octroyer certains droits. Assurez-vous toujours que cela ne peut pas arriver. https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

c: L'utilisation d'un cookie avec un identifiant de session ne serait pas utile pour valider votre token. Si quelqu'un veut détourner la session d'un utilisateur lambda, il lui suffit d'utiliser un sniffer (par exemple: wirehark). Ce hacker aurait les deux informations en même temps.

  • 2: C'est la même chose pour chaque secret. Il y a toujours moyen de le savoir.

La façon dont je le gère est liée au point 1.a. : J'ai un secret mélangé avec une variable aléatoire. Le secret est unique pour chaque jeton.

Cependant, j'essaie de comprendre les meilleures pratiques pour savoir exactement comment et dans quelle mesure le jeton doit être validé, pour créer un système vraiment sécurisé.

Si vous voulez la meilleure sécurité possible, vous ne devez pas suivre aveuglément les meilleures pratiques. Le meilleur moyen est de comprendre ce que vous faites (je pense que c'est correct quand je vois votre question), puis d'évaluer la sécurité dont vous avez besoin. Et si le Mossad veut avoir accès à vos données confidentielles, il trouvera toujours un moyen. (J'aime ce billet de blog: https://www.schneier.com/blog/archives/2015/08/mickens_on_secu.html )


Bon point pour avoir un secret unique pour chaque jeton mais comment créer un secret unique à chaque fois? J'utilise la bibliothèque nimbus jwt
kittu

1
utilisez probablement le mot de passe de hachage de votre utilisateur.
momokjaaaaa

1
«Si vous ne faites pas les choses de la même manière que les autres, il sera plus difficile pour les gens de trouver un moyen de traverser votre sécurité.» Cela ressemble à la sécurité par l'obscurité pour moi. Les meilleures pratiques sont appelées ainsi parce qu'elles atténuent les risques les plus courants d'une manière pratique.
Mnebuerquo

@Mnebuerquo Je suis tout à fait d'accord avec vous, le gars qui a écrit ça ne doit pas faire confiance ;-)
Deblaton Jean-Philippe

1
Il a cependant raison de ne pas suivre aveuglément les meilleures pratiques. Il est bon de comprendre pourquoi les meilleures pratiques sont considérées comme les meilleures . Dans chaque décision de conception de sécurité, il y a un compromis entre sécurité et convivialité. Comprendre le pourquoi signifie que vous pouvez prendre ces décisions intelligemment. (Continuez à suivre les meilleures pratiques, car vos utilisateurs ne le feront pas.)
Mnebuerquo

3

Beaucoup de bonnes réponses ici. Je vais intégrer certaines des réponses qui me semblent les plus pertinentes et ajouter d'autres suggestions.

1) La validation du jeton JWT devrait-elle être limitée à la vérification de la signature du jeton lui-même, en s'appuyant uniquement sur l'intégrité du secret du serveur, ou accompagnée d'un mécanisme de validation distinct?

Non, pour des raisons indépendantes de la compromission d'un jeton secret. Chaque fois qu'un utilisateur se connecte via un nom d'utilisateur et un mot de passe, le serveur d'autorisation doit stocker soit le jeton qui a été généré, soit des métadonnées sur le jeton qui a été généré. Considérez ces métadonnées comme un enregistrement d'autorisation. Une paire utilisateur et application donnée ne doit avoir qu'un seul jeton valide, ou autorisation, à un moment donné. Les métadonnées utiles sont l'ID utilisateur associé au jeton d'accès, l'ID d'application et l'heure à laquelle le jeton d'accès a été émis (ce qui permet la révocation des jetons d'accès existants et l'émission d'un nouveau jeton d'accès). À chaque demande d'API, vérifiez que le jeton contient les métadonnées appropriées. Vous devez conserver les informations sur la date d'émission de chaque jeton d'accès, afin qu'un utilisateur puisse révoquer les jetons d'accès existants si les informations d'identification de son compte sont compromises, se reconnecter et commencer à utiliser un nouveau jeton d'accès. Cela mettra à jour la base de données avec l'heure à laquelle le jeton d'accès a été émis (l'heure d'autorisation créée). Sur chaque demande d'API, vérifiez que l'heure d'émission du jeton d'accès est postérieure à l'heure d'autorisation créée.

D'autres mesures de sécurité comprenaient la non-journalisation des JWT et la nécessité d'un algorithme de signature sécurisé tel que SHA256.

2) Si la vérification de la signature JWT est le seul moyen de valider les jetons, ce qui signifie que l'intégrité du secret du serveur est le point de rupture, comment les secrets du serveur doivent-ils être gérés?

La compromission des secrets du serveur permettrait à un attaquant d'émettre des jetons d'accès pour n'importe quel utilisateur, et le stockage des données des jetons d'accès à l'étape 1 n'empêcherait pas nécessairement le serveur d'accepter ces jetons d'accès. Par exemple, supposons qu'un utilisateur a reçu un jeton d'accès, puis plus tard, un attaquant génère un jeton d'accès pour cet utilisateur. L'heure d'autorisation du jeton d'accès serait valide.

Comme le dit Akshay Dhalwala, si votre secret côté serveur est compromis, vous avez de plus gros problèmes à résoudre car cela signifie qu'un attaquant a compromis votre réseau interne, votre référentiel de code source ou les deux.

Cependant, un système pour atténuer les dommages d'un secret de serveur compromis et éviter de stocker des secrets dans le code source implique une rotation de jeton de secret à l'aide d'un service de coordination comme https://zookeeper.apache.org. Utilisez une tâche cron pour générer un secret d'application toutes les quelques heures environ (quelle que soit la durée de validité de vos jetons d'accès) et transmettez le secret mis à jour à Zookeeper. Dans chaque serveur d'applications qui a besoin de connaître le secret du jeton, configurez un client ZK qui est mis à jour chaque fois que la valeur du nœud ZK change. Stockez un secret principal et un secret secondaire, et chaque fois que le secret de jeton est modifié, définissez le nouveau secret de jeton sur le secret principal et l'ancien secret de jeton sur le secondaire. De cette façon, les jetons valides existants seront toujours valides car ils seront validés par rapport au secret secondaire. Au moment où le secret secondaire est remplacé par l'ancien secret principal, tous les jetons d'accès émis avec le secret secondaire expireront de toute façon.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.