Pour un nouveau projet node.js sur lequel je travaille, je pense à passer d'une approche de session basée sur les cookies (par cela, je veux dire, stocker un identifiant dans un magasin de valeurs-clés contenant des sessions utilisateur dans le navigateur d'un utilisateur) à une approche de session basée sur des jetons (pas de magasin de valeurs-clés) à l'aide de jetons Web JSON (jwt).
Le projet est un jeu qui utilise socket.io - avoir une session basée sur des jetons serait utile dans un tel scénario où il y aura plusieurs canaux de communication dans une seule session (web et socket.io)
Comment fournir une invalidation de jeton / session à partir du serveur en utilisant l'approche jwt?
Je voulais aussi comprendre quels pièges / attaques courants (ou peu communs) je devais rechercher avec ce genre de paradigme. Par exemple, si ce paradigme est vulnérable aux mêmes / différents types d'attaques que l'approche basée sur le magasin de session / cookie.
Donc, disons que j'ai ce qui suit (adapté de ceci et de cela ):
Connexion au magasin de sessions:
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 basée sur les jetons:
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});
});
}
-
Une déconnexion (ou invalidation) pour l'approche du magasin de sessions 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 les informations qui existeraient normalement dans le magasin de valeurs-clés.
la source
isRevoked
option ou tenter de répliquer la même fonctionnalité. github.com/auth0/express-jwt#revoked-tokensRéponses:
Moi aussi, j'ai fait des recherches sur cette question, et bien qu'aucune des idées ci-dessous ne soit une solution complète, elles pourraient 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é côté serveur, mais cela arrête un attaquant en supprimant le jeton d'existence (c'est-à-dire qu'ils devraient avoir volé le jeton avant de se déconnecter).
2) Créez une liste noire de jetons
Vous pouvez stocker les jetons non valides jusqu'à leur date d'expiration initiale et les comparer aux demandes entrantes. Cela semble cependant nier la raison pour laquelle il est entièrement basé sur des jetons, car vous devrez toucher la base de données pour chaque demande. La taille du stockage serait probablement inférieure, car vous n'auriez besoin que de stocker des jetons qui se situaient entre la déconnexion et l'heure d'expiration (c'est un sentiment instinctif et dépend certainement du contexte).
3) Gardez les durées d'expiration des jetons courtes et faites-les tourner souvent
Si vous conservez les durées d'expiration des jetons à des intervalles suffisamment courts et que le client en cours d'exécution effectue le suivi et demande des mises à jour si nécessaire, le numéro 1 fonctionnerait efficacement comme un système de déconnexion complet. Le problème avec cette méthode est qu'elle rend impossible la connexion de l'utilisateur entre les fermetures du code client (selon la durée de l'intervalle d'expiration).
Des plans d'urgence
En cas d'urgence ou si un jeton d'utilisateur a été compromis, vous pouvez autoriser l'utilisateur à modifier un ID de recherche d'utilisateur sous-jacent avec ses informations de connexion. Cela rendrait tous les jetons associés invalides, car l'utilisateur associé ne pourrait plus être trouvé.
Je voulais également noter que c'est une bonne idée d'inclure la dernière date de connexion avec le jeton, afin que vous puissiez appliquer une reconnexion après une période de temps éloignée.
En termes de similitudes / différences en ce qui concerne les attaques utilisant des jetons, ce message répond à la question: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown
la source
2)
précède. Bien que cela fonctionne bien, personnellement, je ne vois pas beaucoup de différence avec les magasins de sessions traditionnels. Je suppose que l'exigence de stockage serait inférieure, mais vous avez toujours besoin d'une base de données. Le plus grand attrait de JWT pour moi était de ne pas utiliser du tout de base de données pour les sessions.Les idées affichées ci-dessus sont bonnes, mais un moyen très simple et facile d'invalider tous les JWT existants consiste simplement à changer le secret.
Si votre serveur crée le JWT, le signe avec un secret (JWS) puis l'envoie au client, le simple changement du secret invalidera tous les jetons existants et exigera que tous les utilisateurs obtiennent un nouveau jeton pour s'authentifier car leur ancien jeton devient soudainement invalide selon au serveur.
Il ne nécessite aucune modification du contenu réel du jeton (ou de l'ID de recherche).
De toute évidence, cela ne fonctionne que pour un cas d'urgence lorsque vous souhaitez que tous les jetons existants expirent, pour l'expiration par jeton, l'une des solutions ci-dessus est requise (comme un court délai d'expiration du jeton ou invalider une clé stockée à l'intérieur du jeton).
la source
Il s'agit principalement d'un long commentaire soutenant et s'appuyant sur la réponse de @mattway
Donné:
Certaines des autres solutions proposées sur cette page préconisent de frapper le magasin de données à chaque demande. Si vous frappez la banque de données principale 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 rendu JWT avec état, au lieu d'être sans état si vous allez à chaque fois dans le magasin de données.
(Si votre site reçoit un volume élevé de demandes non autorisées, JWT les refuserait sans toucher au magasin de données, ce qui est utile. Il existe probablement d'autres cas d'utilisation comme celui-ci.)
Donné:
Une authentification JWT véritablement sans état ne peut pas être obtenue pour une application Web typique, car le JWT sans état n'a aucun moyen de fournir une sécurité immédiate et sécurisée pour les cas d'utilisation importants suivants:
Le compte de l'utilisateur est supprimé / bloqué / suspendu.
Le mot de passe de l'utilisateur a été modifié.
Les rôles ou autorisations de l'utilisateur sont modifiés.
L'utilisateur est déconnecté par l'administrateur.
Toutes les autres données critiques de l'application dans le jeton JWT sont modifiées par l'administrateur du site.
Vous ne pouvez pas attendre l'expiration du jeton dans ces cas. L'invalidation du jeton doit avoir lieu immédiatement. En outre, vous ne pouvez pas faire confiance au client pour ne pas conserver et utiliser une copie de l'ancien jeton, que ce soit avec une 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 jetons sera assez petite par rapport au nombre total d'utilisateurs, car elle n'a qu'à conserver les jetons sur liste noire jusqu'à leur expiration. Je mettrais en œuvre en plaçant des jetons invalides dans redis, memcached ou une autre banque de données en mémoire qui prend en charge la définition d'un délai d'expiration sur une clé.
Vous devez toujours appeler votre base de données en mémoire pour chaque demande d'authentification qui passe l'authentification JWT initiale, mais vous n'avez pas à y stocker les clés de l'ensemble de vos utilisateurs. (Ce qui peut ou non être un gros problème pour un site donné.)
la source
If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
Je garderais un enregistrement du numéro de version jwt sur le modèle utilisateur. Les nouveaux jetons jwt définiraient leur version sur ceci.
Lorsque vous validez le jwt, vérifiez simplement qu'il a un numéro de version égal à la version jwt actuelle de l'utilisateur.
Chaque fois que vous souhaitez invalider d'anciens jwts, il vous suffit de déplacer le numéro de version des utilisateurs jwt.
la source
Je n'ai pas encore essayé cela, 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 utilisateur. La plupart des autres solutions nécessitent une recherche de base de données par demande vers un magasin de sessions utilisateur. C'est très bien dans certains scénarios, mais cela a été créé dans le but d'éviter de tels appels et de rendre très petit l'état côté serveur requis. Vous finirez par recréer une session côté serveur, même petite pour fournir toutes les fonctionnalités d'invalidation forcée. Mais si vous voulez le faire, voici l'essentiel:
Buts:
La solution:
Cela vous oblige à maintenir 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'ID utilisateur. Cette liste noire n'est vérifiée que lors d'une demande de jeton d'actualisation. Les entrées sont nécessaires pour y vivre aussi longtemps que le jeton d'actualisation TTL. Une fois le jeton d'actualisation expiré, l'utilisateur devra se reconnecter.
Les inconvénients:
Avantages:
Avec cette solution, un magasin de données en mémoire comme reddis n'est pas nécessaire, du moins pas pour les informations utilisateur, car le serveur ne fait un appel db que toutes les 15 minutes environ. Si vous utilisez reddis, le stockage d'une liste de sessions valides / invalides serait une solution très rapide et plus simple. Pas besoin de jeton d'actualisation. Chaque jeton d'authentification aurait un identifiant de session et un identifiant de périphérique, ils pourraient être stockés dans une table reddis lors de la création et invalidés le cas échéant. Ensuite, ils seraient vérifiés à chaque demande et rejetés lorsqu'ils n'étaient pas valides.
la source
Une approche que j'ai envisagée est de toujours avoir une valeur
iat
(émise à) dans le JWT. Ensuite, lorsqu'un utilisateur se déconnecte, stockez cet horodatage sur l'enregistrement utilisateur. Lors de la validation du JWT, il suffit de compareriat
le dernier horodatage déconnecté. Si laiat
ancien est plus ancien, il n'est pas valide. Oui, vous devez aller à la base de données, mais je retirerai toujours l'enregistrement utilisateur de toute façon si le JWT est par ailleurs valide.L'inconvénient majeur que je vois est que cela les déconnecterait de toutes leurs sessions s'ils sont dans plusieurs navigateurs ou s'ils ont également un client mobile.
Cela pourrait également être un bon mécanisme pour invalider tous les JWT d'un système. Une partie de la vérification peut concerner un horodatage global de la dernière
iat
heure valide .la source
token_valid_after
, ou quelque chose. Impressionnant!Je suis un peu en retard ici, mais je pense avoir une solution décente.
J'ai une colonne "last_password_change" dans ma base de données qui stocke la date et l'heure de la dernière modification du mot de passe. 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 l'émission du jeton et s'il s'agissait du jeton, même s'il n'est pas encore expiré.
la source
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
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érez une nouvelle chaîne aléatoire, stockez-la dans le champ last_key_used et ajoutez-la à la charge utile lors de la signature du jeton.
Lorsque l'utilisateur se connecte à l'aide du jeton, vérifiez que last_key_used dans la base de données correspond à celui du jeton.
Ensuite, lorsque l'utilisateur se déconnecte par exemple, ou si vous souhaitez invalider le jeton, changez simplement ce champ "last_key_used" en une autre valeur aléatoire et toutes les vérifications ultérieures échoueront, obligeant ainsi l'utilisateur à se connecter avec l'utilisateur et à repasser.
la source
Gardez une liste en mémoire comme celle-ci
Si vos jetons expirent dans une semaine, nettoyez ou ignorez les enregistrements plus anciens. Conservez également uniquement l'enregistrement le plus récent de chaque utilisateur. La taille de la liste dépendra de la durée de conservation de vos jetons et de la fréquence à laquelle les utilisateurs révoqueront leurs jetons. Utilisez db uniquement lorsque la table change. Chargez le tableau en mémoire au démarrage de votre application.
la source
------------------------ Un peu en retard pour cette réponse mais peut-être que cela aidera à quelqu'un ------------- -----------
Du côté client , le moyen le plus simple consiste à supprimer le jeton du stockage du navigateur.
Mais que faire si vous voulez détruire le jeton sur le serveur Node -
Le problème avec le package JWT est qu'il ne fournit aucune méthode ni aucun moyen de détruire le jeton. Vous pouvez utiliser différentes méthodes concernant JWT qui sont mentionnées ci-dessus. Mais ici je vais avec le jwt-redis.
Donc, afin de détruire le jeton sur le côté serveur, vous pouvez utiliser le package jwt-redis au lieu de JWT
Cette bibliothèque (jwt-redis) répète complètement toutes les fonctionnalités de la bibliothèque jsonwebtoken, avec un ajout important. Jwt-redis vous permet de stocker l'étiquette de jeton dans redis pour vérifier la validité. L'absence d'une étiquette de jeton dans redis rend le jeton non valide. Pour détruire le jeton dans jwt-redis, il existe une méthode destroy
cela fonctionne de cette façon:
1) Installez jwt-redis à partir de npm
2) Pour créer -
3) Pour vérifier -
4) Détruire -
Remarque : vous pouvez fournir expiresIn lors de la connexion du jeton de la même manière que celle fournie dans JWT.
Peut-être que cela aidera quelqu'un
la source
Pourquoi ne pas simplement utiliser la revendication jti (nonce) et la stocker dans une liste en tant que champ d'enregistrement utilisateur (dépendant de la base de données, mais au moins une liste séparée par des virgules convient)? Pas besoin de recherche distincte, comme d'autres l'ont fait remarquer, vous voulez probablement obtenir l'enregistrement utilisateur de toute façon, et de cette façon, vous pouvez avoir plusieurs jetons valides pour différentes instances clientes ("se déconnecter partout" peut réinitialiser la liste à vide)
la source
Pour la validation du jeton, vérifiez d'abord l'heure d'expiration du jeton, puis la liste noire si le jeton n'a pas expiré.
Pour les longs besoins de session, il devrait y avoir un mécanisme pour prolonger le délai d'expiration du jeton.
la source
Tard dans la soirée, MES deux cents sont donnés ci-dessous après quelques recherches. Pendant la déconnexion, assurez-vous que les choses suivantes se produisent ...
Effacer le stockage / la session client
Mettre à jour la date et l'heure de la dernière connexion de la table utilisateur et la date et l'heure de déconnexion chaque fois que la connexion ou la déconnexion se produit respectivement. Ainsi, l'heure de la date de connexion doit toujours être supérieure à la déconnexion (ou garder la date de déconnexion nulle si l'état actuel est la connexion et pas encore la déconnexion)
C'est bien plus simple que de garder un tableau supplémentaire de liste noire et de purger régulièrement. La prise en charge de plusieurs appareils nécessite un tableau supplémentaire pour conserver les dates de connexion, de déconnexion avec des détails supplémentaires tels que les détails du système d'exploitation ou du client.
la source
Chaîne unique par utilisateur et chaîne globale hachée ensemble
pour servir de partie secrète JWT, permettre l'invalidation des jetons individuels et globaux. Flexibilité maximale au prix d'une recherche / lecture de base de données pendant l'authentification de la demande. Ils sont également faciles à mettre en cache, car ils changent rarement.Voici un exemple:
par exemple, utilisation https://jwt.io (pas sûr qu'ils gèrent les secrets dynamiques de 256 bits)
la source
Je l'ai fait de la manière suivante:
unique hash
, puis stockez-le dans redis et votre JWT . Cela peut être appelé une sessionAinsi, lorsqu'un utilisateur se connecte, un hachage unique est créé, stocké dans redis et injecté dans votre JWT .
Lorsqu'un utilisateur essaie de visiter un point de terminaison protégé, vous récupérez le hachage de session unique de votre JWT , interrogez redis et voyez s'il s'agit d'une correspondance!
Nous pouvons prolonger 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, la stockons dans notre JWT , puis mettons la précédente sur liste noire.
Cela signifie que le JWT est en constante évolution et empêche les JWT périmés d'être piratés, volés ou autre chose.
la source
aud
etjti
réclamations dans JWT, vous êtes sur la bonne voie.Si vous souhaitez pouvoir révoquer des jetons utilisateur, vous pouvez garder une trace de tous les jetons émis sur votre base de données et vérifier s'ils sont valides (existent) sur une table de type session. L'inconvénient est que vous atteindrez la base de données à chaque demande.
Je ne l'ai pas essayé, mais je suggère la méthode suivante pour autoriser la révocation de jetons tout en limitant les hits de base de données -
Pour réduire le taux de vérification de la base de données, divisez tous les jetons JWT émis en groupes X selon une association déterministe (par exemple, 10 groupes par premier chiffre de l'ID utilisateur).
Chaque jeton JWT contiendra l'ID de groupe et un horodatage créé lors de la création du jeton. par exemple,
{ "group_id": 1, "timestamp": 1551861473716 }
Le serveur conservera tous les identifiants de groupe en mémoire et chaque groupe aura un horodatage qui indique à quand remonte le dernier événement de déconnexion d'un utilisateur appartenant à ce groupe. par exemple,
{ "group1": 1551861473714, "group2": 1551861487293, ... }
Les demandes avec un jeton JWT qui ont un horodatage de groupe plus ancien seront vérifiées pour la validité (hit DB) et si valides, un nouveau jeton JWT avec un nouvel horodatage sera émis pour une utilisation future par le client. Si l'horodatage de groupe du jeton est plus récent, nous faisons confiance au JWT (aucun hit DB).
Donc -
la source
Si l'option "déconnexion de tous les appareils" est acceptable (dans la plupart des cas, elle l'est):
Un trajet de base de données pour obtenir l'enregistrement utilisateur dans la plupart des cas est de toute façon nécessaire, ce qui n'ajoute pas beaucoup de frais généraux au processus de validation. Contrairement à la gestion d'une liste noire, où la charge de base de données est importante en raison de la nécessité d'utiliser une jointure ou un appel distinct, nettoyer les anciens enregistrements, etc.
la source
Je vais répondre si nous devons fournir une fonctionnalité de déconnexion de tous les appareils lorsque nous utilisons JWT. Cette approche utilisera des recherches de base de données pour chaque demande. Parce que nous avons besoin d'un état de sécurité persistant même en cas de panne du serveur. Dans la table utilisateur, nous aurons deux colonnes
Chaque fois qu'il y a une demande de déconnexion de l'utilisateur, nous mettrons à jour LastValidTime à l'heure actuelle et Logged-In à false. S'il y a une demande de connexion, nous ne changerons pas LastValidTime mais Logged-In sera défini sur true.
Lorsque nous créons le JWT, nous aurons l'heure de création du JWT dans la charge utile. Lorsque nous autorisons un service, nous vérifions 3 conditions
Voyons un scénario pratique.
L'utilisateur X a deux appareils A, B. Il s'est connecté à notre serveur à 19 heures en utilisant l'appareil A et l'appareil B. (disons que le délai d'expiration JWT est de 12 heures). A et B ont tous les deux JWT avec createdTime: 7pm
À 21 heures, il a perdu son appareil B. Il se déconnecte immédiatement de l'appareil A. Cela signifie que maintenant, notre entrée d'utilisateur de base de données X a LastValidTime comme "ThatDate: 9: 00: xx: xxx" et connecté comme "false".
À 9h30, le Mr.Thief essaie de se connecter en utilisant le périphérique B. Nous vérifierons la base de données même si la connexion est fausse, donc nous ne le permettrons pas.
À 22 h, M.X se connecte à partir de son appareil A. Maintenant, l'appareil A a JWT avec l'heure de création: 22 h. La base de données connectée est désormais définie sur "true"
À 22h30, Mr.Thief essaie de se connecter. Même si la connexion est vraie. Le LastValidTime est 21 h dans la base de données mais le JWT de B a créé l'heure à 19 h. Il ne sera donc pas autorisé à accéder au service. Donc, en utilisant le périphérique B sans avoir le mot de passe, il ne peut pas utiliser le JWT déjà créé après la déconnexion d'un périphérique.
la source
Une solution IAM comme Keycloak (sur laquelle j'ai travaillé) fournit un point de terminaison de révocation de jetons comme
Point de terminaison de révocation de jeton
/realms/{realm-name}/protocol/openid-connect/revoke
Si vous souhaitez simplement déconnecter un agent utilisateur (ou utilisateur), vous pouvez également appeler un point de terminaison (cela invaliderait simplement les jetons). Encore une fois, dans le cas de Keycloak, la partie utilisatrice doit simplement appeler le point de terminaison
/realms/{realm-name}/protocol/openid-connect/logout
Lien au cas où vous voulez en savoir plus
la source
Cela semble vraiment difficile à résoudre sans une recherche de base de données à chaque vérification de jeton. L'alternative à laquelle je pense est de conserver une liste noire de jetons invalides côté serveur; qui doit être mis à jour sur une base de données chaque fois qu'un changement survient pour conserver les changements à travers les redémarrages, en faisant vérifier par le serveur la base de données au redémarrage pour charger la liste noire actuelle.
Mais si vous le conservez dans la mémoire du serveur (une variable globale de toutes sortes), il ne sera pas évolutif sur plusieurs serveurs si vous en utilisez plusieurs, donc dans ce cas, vous pouvez le conserver sur un cache Redis partagé, qui devrait être mis en place pour conserver les données quelque part (base de données? système de fichiers?) au cas où il devrait être redémarré, et chaque fois qu'un nouveau serveur est lancé, il doit s'abonner au cache Redis.
Alternative à une liste noire, en utilisant la même solution, vous pouvez le faire avec un hachage enregistré en redis par session comme le souligne cette autre réponse (pas sûr que ce serait plus efficace avec de nombreux utilisateurs se connectant cependant).
Cela vous semble-t-il terriblement compliqué? ça me fait!
Avertissement: je n'ai pas utilisé Redis.
la source
Si vous utilisez axios ou une bibliothèque de requêtes http basée sur une promesse similaire, vous pouvez simplement détruire le jeton sur le front-end à l'intérieur de la
.then()
pièce. Il sera lancé dans la partie réponse .then () une fois que l'utilisateur aura exécuté cette fonction (le code de résultat du point de terminaison du serveur doit être correct, 200). Une fois que l'utilisateur a cliqué sur cette route lors de la recherche de données, si le champ de la base de donnéesuser_enabled
est faux, il déclenchera la destruction du jeton et l'utilisateur sera immédiatement déconnecté et empêché d'accéder aux routes / pages protégées. Nous n'avons pas à attendre l'expiration du jeton pendant que l'utilisateur est connecté en permanence.la source
Je viens d'enregistrer le jeton dans la table des utilisateurs, lors de la connexion de l'utilisateur, je mettrai à jour le nouveau jeton et lorsque l'auth sera égal au jwt actuel de l'utilisateur.
Je pense que ce n'est pas la meilleure solution mais ça marche pour moi.
la source
Stateless JWT
etStateful JWT
(qui est très similaire aux sessions).Stateful JWT
peut bénéficier du maintien d'une liste blanche de jetons.