Invalidation des jetons Web JSON

Pour un nouveau projet node.js sur lequel je travaille, je pense à passer d'une approche de session basée sur les cookies (par là, je veux dire, stocker un identifiant dans un magasin à valeurs clés contenant des sessions utilisateur dans le navigateur d'un utilisateur) À une approche de session basée sur les jetons (sans magasin à valeur clé) à l'aide des jetons Web JSON (jwt).

Le projet est un jeu qui utilise socket.io – une session basée sur jetons serait utile dans un tel scénario où il y aura plusieurs canaux de communication en une seule session (web et socket.io)

Comment pourrait-on fournir une invalidation de token / session du serveur à l'aide de l'approche jwt?

Je voulais également comprendre quels pièges / attaques communes (ou peu communes) je devais surveiller avec ce genre de paradigme. Par exemple, si ce paradigme est vulnérable aux mêmes types d'attaques que la démarche de session / cookie.

Alors, dis-je que j'ai ce qui suit (adapté de ceci et ceci ):

Session Store Login:

app.get('/login', function(request, response) { var user = {username: request.body.username, password: request.body.password }; // Validate somehow validate(user, function(isValid, profile) { // Create session token var token= createSessionToken(); // Add to a key-value database KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}}); // The client should save this session token in a cookie response.json({sessionToken: token}); }); } 

Connexion par jeton:

 var jwt = require('jsonwebtoken'); app.get('/login', function(request, response) { var user = {username: request.body.username, password: request.body.password }; // Validate somehow validate(user, function(isValid, profile) { var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60}); response.json({token: token}); }); } 

Un déconnexion (ou invalide) pour l'approche Store Session nécessiterait une mise à jour de la base de données KeyValueStore avec le jeton spécifié.

Il semble qu'un tel mécanisme n'existerait pas dans l'approche basée sur les jetons car le jeton lui-même contiendrait l'information qui existerait normalement dans le magasin à valeur ajoutée.

Moi aussi, j'ai étudié cette question et, même si aucune des idées ci-dessous ne sont des solutions complètes, elles peuvent aider d'autres personnes à exclure des idées ou à en fournir d'autres.

1) Retirez simplement le jeton du client

Évidemment, cela ne fait rien pour la sécurité du côté du serveur, mais il arrête un attaquant en supprimant le jeton de l'existence (c'est-à-dire qu'ils auraient dû voler le jeton avant le déconnexion).

2) Créer une liste noire token

Vous pouvez stocker les jetons invalides jusqu'à leur date d'expiration initiale et les comparer aux demandes reçues. Cela semble annuler la raison pour laquelle on a totalisé le token en premier lieu, car il faudrait toucher la base de données pour chaque demande. La taille de stockage serait probablement plus faible, car vous ne devriez avoir besoin que d'entreposer des jetons qui se trouvaient entre le temps de connexion et le délai d'expiration (c'est une intuition et dépend définitivement du contexte).

3) Gardez simplement les temps d'expiration des jetons courts et tournez-les souvent

Si vous gardez les temps d'expiration des jetons à des intervalles assez courts, et que le client en cours de suivi et demande des mises à jour si nécessaire, le numéro 1 fonctionnerait efficacement comme un système complet de déconnexion. Le problème avec cette méthode est qu'il rend impossible de garder l'utilisateur connecté entre les fermetures du code client (selon la durée de l'intervalle d'expiration).

Des plans d'urgence

S'il y avait une urgence ou qu'un jeton d'utilisateur a été compromis, une chose que vous pouvez faire est de permettre à l'utilisateur de modifier une ID de recherche d'utilisateur sous-jacente avec ses identifiants de connexion. Cela rendrait inactifs tous les jetons associés, car l'utilisateur associé ne pourrait plus être trouvé.

Je voulais également noter qu'il est judicieux d'inclure la dernière date de connexion avec le jeton, afin que vous puissiez faire une relogine après une période de temps éloignée.

En termes de similitudes / différences en ce qui concerne les attaques utilisant des jetons, cette publication traite de la question: http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

Les idées affichées ci-dessus sont bonnes, mais un moyen très simple et simple d'invalider tous les JWT existants est simplement de changer le secret.

Si votre serveur crée le JWT, le signe avec un secret (JWS) puis l'envoie au client, il suffit de changer le secret invalidant tous les jetons existants et obligera tous les utilisateurs à gagner un nouveau jeton pour s'authentifier car leur ancien jeton devient tout à fait invalide selon Au serveur.

Il ne nécessite aucune modification du contenu réel du jeton (ou identifiant de recherche).

De toute évidence, cela ne fonctionne que pour un cas d'urgence lorsque vous vouliez que tous les jetons existants expirent, pour l'expiration d'un jeton, une des solutions ci-dessus est requise (comme l'expiration de token court ou l'invalidation d'une clé mémorisée dans le jeton).

Je garderais un enregistrement du numéro de version jwt sur le modèle utilisateur. Les nouveaux jetons jwt définiraient leur version.

Lorsque vous validez le jwt, vérifiez simplement qu'il a un numéro de version égal à la version jwt actuelle des utilisateurs.

Chaque fois que vous souhaitez invalider les anciennes jwts, il suffit de faire tomber le numéro de version de l'utilisateur jwt.

C'est principalement un long commentaire qui soutient et s'appuie sur la réponse par @mattway

Donné:

Certaines des autres solutions proposées sur cette page recommandent de frapper le magasin de données sur toutes les demandes. Si vous accédez au magasin de données principal pour valider chaque demande d'authentification, je vois moins de raisons d'utiliser JWT au lieu d'autres mécanismes d'authentification de jeton établis. Vous avez essentiellement fait de JWT stateful, au lieu d'apatrides si vous accédez au magasin de données à chaque fois.

(Si votre site reçoit un volume élevé de demandes non autorisées, JWT les refuserait sans toucher le magasin de données, ce qui est utile. Il y a probablement d'autres cas d'utilisation comme ça.)

Donné:

L'authentification JWT véritablement apatride ne peut être obtenue pour une application Web mondiale typique, car l'apatrid JWT n'a pas le moyen de fournir un support immédiat et sécurisé pour les cas d'utilisation importants suivants:

Le compte de l'utilisateur est supprimé / bloqué / suspendu.

Le mot de passe de l'utilisateur est modifié.

Les rôles ou les autorisations de l'utilisateur sont modifiés.

L'utilisateur est déconnecté par l'administrateur.

Toute autre donnée critique de l'application dans le jeton JWT est modifiée par l'administrateur du site.

Vous ne pouvez pas attendre l'expiration du jeton dans ces cas. L'invalidation de jeton doit se produire immédiatement. En outre, vous ne pouvez pas faire confiance au client de ne pas conserver et utiliser une copie de l'ancien jeton, que ce soit avec intention malveillante ou non.

Par conséquent: Je pense que la réponse de @ matt-way, # 2 TokenBlackList, serait le moyen le plus efficace d'ajouter l'état requis à l'authentification basée sur JWT.

Vous avez une liste noire qui contient ces jetons jusqu'à ce que leur date d'expiration soit atteinte. La liste des tokens sera assez petite par rapport au nombre total d'utilisateurs, car il ne faut que garder les tokens de la liste noire jusqu'à leur expiration. Je mettrais en place des jetons invalides dans redis, memcached ou un autre magasin de mémoire en mémoire qui prend en charge la définition d'un délai d'expiration sur une clé.

Vous devez toujours appeler votre db en mémoire pour chaque demande d'authentification qui passe l'authentification JWT initiale, mais vous ne devez pas stocker les clés pour l'ensemble de vos utilisateurs. (Ce qui peut ou non être une grosse affaire pour un site donné).

Je n'ai pas essayé cela encore, et il utilise beaucoup d'informations basées sur certaines des autres réponses. La complexité ici est d'éviter un appel de magasin de données côté serveur par demande d'informations sur l'utilisateur. La plupart des autres solutions nécessitent une recherche db par demande à un magasin de session utilisateur. Cela est bien dans certains scénarios, mais cela a été créé dans le but d'éviter de tels appels et de rendre tout l'état du serveur requis pour être très petit. Vous finirez par recréer une session côté serveur, même si elle est petite pour fournir toutes les fonctionnalités d'invalidation de la force. Mais si vous voulez le faire, voici la règle:

Buts:

  • Atténuer l'utilisation d'un magasin de données (sans état).
  • Possibilité de forcer le déconnexion de tous les utilisateurs.
  • Capacité à forcer à déconnecter un individu à tout moment.
  • Possibilité d'exiger la réintégration du mot de passe après un certain temps.
  • Possibilité de travailler avec plusieurs clients.
  • Capacité de forcer un réexécution lorsque l'utilisateur clique sur le déconnexion d'un client particulier. (Pour éviter que quelqu'un ne supprime un jeton de client après que l'utilisateur se retire – voir les commentaires pour des informations supplémentaires)

La solution:

  • Utiliser des jetons d'accès de courte durée (<5 m) couplés à un jeton de rafraîchissement stocké par le client à plus longue durée (quelques heures).
  • Chaque demande vérifie soit la date d'expiration de l'auth ou le rafraîchissement des jetons pour la validité.
  • Lorsque le jeton d'accès expire, le client utilise le jeton de rafraîchissement pour actualiser le jeton d'accès.
  • Au cours de la vérification des jetons de rafraîchissement, le serveur vérifie une petite liste noire d'identifiants utilisateur – si elle a été rejetée, la demande de rafraîchissement.
  • Lorsqu'un client ne dispose pas d'un jeton d'actualisation ou d'authentification valide (non expiré), l'utilisateur doit se connecter à nouveau, car toutes les autres demandes seront rejetées.
  • Lors de la demande de connexion, vérifiez le stockage des données utilisateur pour l'interdiction.
  • Lors de la connexion – ajoutez cet utilisateur à la liste noire de la session afin qu'ils doivent se reconnecter. Vous devriez stocker des informations supplémentaires pour ne pas les enregistrer sur tous les périphériques dans un environnement multi-périphérique, mais cela pourrait être fait en ajoutant un champ de périphérique à Liste noire des utilisateurs.
  • Pour forcer la réintroduction après x le temps – maintenir la dernière date de connexion dans le jeton d'authentification, et la vérifier par demande.
  • Pour forcer, fermez tous les utilisateurs – réinitialisez la clé hash du jeton.

Cela nécessite que vous conserviez une liste noire (état) sur le serveur, en supposant que la table utilisateur contient des informations utilisateur interdites. La liste noire des sessions invalides – est une liste d'identifiants utilisateur. Cette liste noire n'est vérifiée que lors d'une requête de récapitulation. Les entrées sont nécessaires pour y vivre tant que le TTL de token d'actualisation. Une fois le jeton de rafraîchissement expiré, l'utilisateur devra se connecter à nouveau.

Les inconvénients:

  • Toujours nécessaire pour effectuer une recherche de stock de données sur la demande de token de rafraîchissement.
  • Les jetons invalides peuvent continuer à fonctionner pour le TTL du token d'accès.

Avantages:

  • Fournit la fonctionnalité souhaitée.
  • L'action de sauvegarde actualisée est cachée à l'utilisateur en fonctionnement normal.
  • Il suffit de faire une recherche de stock de données sur les demandes de rafraîchissement au lieu de chaque demande. C'est-à-dire 1 toutes les 15 minutes au lieu de 1 par seconde.
  • Minimise l'état du côté du serveur dans une très petite liste noire.

Avec cette solution, un magasin de données de mémoire comme reddis n'est pas nécessaire, du moins pas pour les informations utilisateur, car le serveur ne fait que faire un appel db toutes les 15 minutes environ. Si vous utilisez Reddis, le stockage d'une liste de session valide / invalide serait une solution très rapide et plus simple. Pas besoin d'un jeton de rafraîchissement. Chaque jeton d'authentification aurait un identifiant de session et un identifiant de périphérique, ils pourraient être stockés dans une table reddistable lors de la création et invalidés le cas échéant. Ensuite, ils seraient vérifiés sur chaque demande et rejetés lorsqu'ils sont invalides.

Une approche que j'ai envisagée est d'avoir toujours une valeur (émise à) dans le JWT. Ensuite, lorsqu'un utilisateur se déconnecte, enregistrez cet horodatage sur l'enregistrement utilisateur. Lors de la validation du JWT, il suffit de comparer le iat au dernier timestamp déconnecté. Si le iat est plus ancien, il n'est pas valide. Oui, vous devez aller à la base de données, mais je vais toujours tirer l'enregistrement de l'utilisateur de toute façon si le JWT est par ailleurs valide.

L'inconvénient majeur que je vois à ce sujet est qu'il les déconnecte de toutes leurs sessions s'ils se trouvent dans plusieurs navigateurs ou ont un client mobile aussi.

Cela pourrait également être un bon mécanisme pour invalider tous les JWT dans un système. Une partie du contrôle pourrait être contraire à un horodatage global du dernier moment valide.

Je suis un peu en retard ici, mais je pense que j'ai une solution décente.

J'ai une colonne "last_password_change" dans ma base de données qui stocke la date et l'heure à laquelle le mot de passe a été modifié pour la dernière fois. Je stocke également la date / heure d'émission dans le JWT. Lors de la validation d'un jeton, je vérifie si le mot de passe a été modifié après la publication du jeton et si le jeton est rejeté même s'il n'a pas encore expiré.

  1. Donner 1 jour d'expiration pour les tokens
  2. Maintenir une liste noire quotidienne.
  3. Mettez les jetons invalides / déconnexion dans la liste noire

Pour la validation des token, vérifiez d'abord l'heure d'expiration du jeton, puis la liste noire si le jeton n'a pas expiré.

Pour les besoins de longues sessions, il devrait y avoir un mécanisme pour prolonger l'expiration des jetons.

Pourquoi ne pas simplement utiliser la revendication jti (nonce) et stocker celle-ci dans une liste en tant que champ d'enregistrement utilisateur (db dépend, mais au moins une liste séparée par des virgules est bien)? Il n'est pas nécessaire de rechercher séparément, comme d'autres ont souligné que vous souhaitez obtenir l'enregistrement de l'utilisateur de toute façon, et de cette façon, vous pouvez avoir plusieurs jetons valides pour différentes instances clients ("déconnexion partout" peut réinitialiser la liste à vide)

Vous pouvez avoir un champ "last_key_used" sur votre base de données sur le document / enregistrement de votre utilisateur.

Lorsque l'utilisateur se connecte avec l'utilisateur et passe, génère une nouvelle chaîne aléatoire, rangez-le dans le champ last_key_used et ajoutez-le à la charge utile lors de la signature du jeton.

Lorsque l'utilisateur se connecte en utilisant le jeton, vérifiez le last_key_used dans le DB pour correspondre à celui du jeton.

Ensuite, lorsque l'utilisateur effectue un déconnexion par exemple, ou si vous souhaitez invalider le jeton, modifiez simplement ce champ "last_key_used" à une autre valeur aléatoire et tous les contrôles ultérieurs échoueront, ce qui obligera l'utilisateur à se connecter à l'utilisateur et à passer à nouveau.

À la fin de la fête, mes deux cents sont donnés ci-dessous après quelques recherches. Pendant la connexion, assurez-vous que les choses suivantes se produisent …

Effacer le stockage / la session du client

Mettre à jour la dernière date de connexion de la dernière date de connexion et la date-heure de déconnexion chaque fois que la connexion ou la connexion se produisent respectivement. Ainsi, la date de connexion doit toujours être supérieure à la fin de session (ou maintenez la date de connexion nulle si l'état actuel est connecté et n'est pas encore déconnecté)

C'est beaucoup plus simple que de garder un tableau supplémentaire de la liste noire et de purger régulièrement. Le support de plusieurs appareils nécessite une table supplémentaire pour conserver la connexion, les dates d'ouverture de session avec des détails supplémentaires, comme les détails du système d'exploitation ou du client.

Chaîne unique par utilisateur et chaîne globale assemblée ensemble

Pour servir de partie secrète JWT permettent l'invalidation de token individuelle et globale. Flexibilité maximale au coût d'une recherche / lecture db pendant l'authentification de la requête. Aussi facile à mettre en cache, car ils changent rarement.

Je l'ai fait de la manière suivante:

  1. Créez un unique hash , puis enregistrez-le en redis et votre JWT . Cela peut être appelé une session
    • Nous enregistrons également le nombre de requêtes effectuées par JWT particulier. Chaque fois qu'un jwt est envoyé au serveur, nous incrémentons le nombre de requêtes . (C'est optionnel)

Donc, lorsqu'un utilisateur se connecte, un hash unique est créé, stocké en redis et injecté dans votre JWT .

Lorsqu'un utilisateur essaie de visiter un point d'extrémité protégé, vous allez saisir le hash de la session unique à partir de votre JWT , rediffuser des requêtes et voir si c'est un match!

Nous pouvons étendre cela et rendre notre JWT encore plus sécurisé, voici comment:

Chaque X demande un JWT particulier, nous générons une nouvelle session unique, rangez-la dans notre JWT , puis liste noire de la précédente.

Cela signifie que le JWT change constamment et empêche les JWT obsolètes d'être piratés, volés ou autre chose.

Il suffit de supprimer le jeton du stockage du navigateur. Je le fais dans mon application angularjs + node et ça marche bien.