Stratégie pour générer des identifiants uniques et sécurisés à utiliser dans une application Web «parfois hors ligne»

47

J'ai un projet Web qui permet aux utilisateurs de travailler en ligne et hors ligne et je cherche un moyen de générer des identifiants uniques pour les enregistrements côté client. J'aimerais une approche qui fonctionne lorsqu'un utilisateur est hors ligne (incapable de parler à un serveur), unique et sécurisée. Par "sécurisé", je m'inquiète tout particulièrement de la part des clients qui soumettent des identifiants en double (à des fins malveillantes ou non), ce qui nuit à l'intégrité des données.

Je fais des recherches sur Google, en espérant que le problème était déjà résolu. Je n'ai rien trouvé de très définitif, en particulier en ce qui concerne les approches utilisées dans les systèmes de production. J'ai trouvé quelques exemples de systèmes dans lesquels les utilisateurs n'accèdent qu'aux données qu'ils ont créées (par exemple, une liste Todo accessible sur plusieurs appareils, mais uniquement par l'utilisateur qui l'a créée). Malheureusement, j'ai besoin de quelque chose d'un peu plus sophistiqué. J'ai trouvé de très bonnes idées ici , qui correspondent à la façon dont je pensais que les choses pourraient fonctionner.

Voici la solution proposée.

Quelques exigences

  1. Les identifiants doivent être globalement uniques (ou du moins uniques dans le système)
  2. Généré sur le client (c.-à-d. Via JavaScript dans le navigateur)
  3. Sécurisé (comme indiqué ci-dessus et autrement)
  4. Les données peuvent être visualisées / éditées par plusieurs utilisateurs, y compris ceux qui ne l'ont pas créé
  5. Ne cause pas de problèmes de performances importants pour les bases de données (telles que MongoDB ou CouchDB)

Solution proposée

Lorsque les utilisateurs créent un compte, ils reçoivent un uuid généré par le serveur et réputé unique dans le système. Cet identifiant ne doit PAS être le même que le jeton d'authentification des utilisateurs. Appelons cet identifiant les utilisateurs "jeton d'identifiant".

Lorsqu'un utilisateur crée un nouvel enregistrement, il génère un nouvel uuid en javascript (généré à l'aide de window.crypto lorsqu'il est disponible. Voir les exemples ici ). Cet identifiant est concaténé avec le "jeton d'identifiant" que l'utilisateur a reçu lors de la création de son compte. Ce nouvel identifiant composite (jeton d'identifiant côté serveur + uuid côté client) est désormais l'identifiant unique de l'enregistrement. Lorsque l'utilisateur est en ligne et soumet ce nouvel enregistrement au serveur principal, le serveur:

  1. Identifiez cela comme une action "insertion" (c.-à-d. Pas une mise à jour ou une suppression)
  2. Validez que les deux parties de la clé composite sont valides
  3. Valider que la partie "id token" fournie de l'id composite est correcte pour l'utilisateur actuel (c'est-à-dire qu'elle correspond au jeton id attribué par le serveur à l'utilisateur lors de la création de son compte).
  4. Si tout est copasetic, insérer les données dans la db (en prenant soin de faire un insert et non un « upsert » de telle sorte que si l'identifiant n'existe déjà , il ne met pas à jour un enregistrement existant par erreur)

Les requêtes, les mises à jour et les suppressions ne nécessitent aucune logique particulière. Ils utiliseraient simplement l'identifiant pour l'enregistrement de la même manière que les applications traditionnelles.

Quels sont les avantages de cette approche?

  1. Le code client peut créer de nouvelles données en mode hors connexion et connaître immédiatement l'identifiant de cet enregistrement. J'ai envisagé d'autres approches dans lesquelles un identifiant temporaire serait généré sur le client, puis remplacé par un identifiant "final" lorsque le système était en ligne. Cependant, cela semblait très fragile. Surtout quand vous commencez à penser à créer des données enfants avec des clés étrangères qui devraient également être mises à jour. Sans parler du traitement des urls qui changeraient lorsque l'identifiant changerait.

  2. En faisant des identifiants un composite d'une valeur générée par le client ET d'une valeur générée par le serveur, chaque utilisateur crée effectivement des identifiants dans un bac à sable. Ceci est destiné à limiter les dommages pouvant être causés par un client malveillant / non autorisé. En outre, toutes les collisions d'identifiant se font par utilisateur, et ne concernent pas l'ensemble du système.

  3. Dans la mesure où un jeton d'identifiant d'utilisateur est lié à leur compte, les identifiants ne peuvent être générés dans un sandbox d'utilisateurs que par des clients authentifiés (c'est-à-dire où l'utilisateur s'est connecté avec succès). Ceci est destiné à empêcher les clients malveillants de créer de mauvais identifiants pour un utilisateur. Bien sûr, si un jeton d'authentification d'utilisateurs était volé par un client malveillant, ils pourraient faire de mauvaises choses. Mais, une fois qu'un jeton d'authentification a été volé, le compte est compromis de toute façon. Si cela se produisait, les dommages causés seraient limités au compte compromis (et non à l'ensemble du système).

Préoccupations

Voici quelques-unes de mes préoccupations avec cette approche

  1. Cela générera-t-il des identifiants suffisamment uniques pour une application à grande échelle? Y a-t-il une raison de penser que cela entraînera des collisions d'identité? Le javascript peut-il générer un uuid suffisamment aléatoire pour que cela fonctionne? Il semble que window.crypto soit assez largement disponible et ce projet nécessite déjà des navigateurs raisonnablement modernes. ( cette question a maintenant sa propre question SO distincte )

  2. Y a-t-il des lacunes qui me manquent qui pourraient permettre à un utilisateur malveillant de compromettre le système?

  3. Existe-t-il une raison de s'inquiéter des performances de la base de données lorsque vous interrogez une clé composite composée de 2 uuids. Comment cet identifiant doit-il être stocké pour de meilleures performances? Deux champs distincts ou un seul champ d'objet? Y aurait-il une "meilleure" approche différente pour Mongo vs Couch? Je sais qu’une clé primaire non séquentielle peut entraîner des problèmes de performances importants lors de l’insertion. Serait-il plus intelligent d’avoir une valeur générée automatiquement pour la clé primaire et de stocker cet identifiant dans un champ séparé? ( cette question a maintenant sa propre question SO distincte )

  4. Avec cette stratégie, il serait facile de déterminer qu'un ensemble particulier d'enregistrements a été créé par le même utilisateur (étant donné qu'ils partageraient tous le même jeton d'identifiant visible publiquement). Bien que je ne voie pas de problèmes immédiats avec cela, il est toujours préférable de ne pas divulguer plus d'informations que nécessaire sur les détails internes. Une autre possibilité consisterait à hacher la clé composite, mais il semble que le problème soit plus grave que sa valeur.

  5. En cas de collision d'identifiant pour un utilisateur, il n'existe pas de moyen simple de récupération. Je suppose que le client pourrait générer un nouvel identifiant, mais cela semble représenter beaucoup de travail pour un cas extrême qui ne devrait vraiment jamais se produire. J'ai l'intention de laisser cette question sans réponse.

  6. Seuls les utilisateurs authentifiés peuvent afficher et / ou modifier des données. Ceci est une limitation acceptable pour mon système.

Conclusion

Est-ce que ci-dessus un plan raisonnable? Je me rends compte en partie que cela revient à un jugement reposant sur une compréhension plus complète de la demande en question.

Herbrandson
la source
Je pense que cette question pourrait vous intéresser. Stackoverflow.com/questions/105034/… De plus, cela me lit comme un GUID mais ils ne semblent pas être natifs en javascript
Rémi
2
Il me semble que les UUID répondent déjà aux 5 exigences énumérées. Pourquoi sont-ils insuffisants?
Gabe
@Gabe Voir mes commentaires sur lie ryans post ci
herbrandson
meta discussion de cette question: meta.stackoverflow.com/questions/251215/…
gnat
"client malveillant / rouge" - voleur.
David Conrad

Réponses:

4

Votre approche fonctionnera. De nombreux systèmes de gestion de documents utilisent ce type d’approche.

Une chose à considérer est que vous n'avez pas besoin d'utiliser à la fois l'utilisateur uuid et l'identifiant d'élément aléatoire dans la chaîne. Vous pouvez plutôt hacher la concaturation des deux. Cela vous donnera un identifiant plus court, et éventuellement d'autres avantages, car les identifiants résultants seront mieux répartis (mieux équilibré pour l'indexation et le stockage de fichiers si vous stockez des fichiers en fonction de leur uuid).

Une autre option consiste à générer uniquement un uuid temporaire pour chaque article. Ensuite, lorsque vous vous connectez et les publiez sur le serveur, celui-ci génère des uuid (garantis) pour chaque élément et vous les renvoie. Vous mettez ensuite à jour votre copie locale.

Grand maître b
la source
2
J'avais envisagé d'utiliser un hash du 2 comme identifiant. Cependant, il ne m'a pas semblé qu'il y avait un moyen approprié de générer un sha256 dans tous les navigateurs que je dois prendre en charge :(
herbrandson le
12

Vous devez séparer les deux préoccupations:

  1. Génération d'identifiant: le client doit pouvoir générer un identifiant unique dans un système distribué
  2. Problème de sécurité: le client DOIT avoir un jeton d'authentification d'utilisateur valide ET le jeton d'authentification est valide pour l'objet créé / modifié

La solution à ces deux problèmes est malheureusement séparée. mais heureusement ils ne sont pas incompatibles.

Le problème de la génération d’ID est facilement résolu en générant avec UUID, c’est pourquoi l’UUID est conçu; le problème de sécurité nécessiterait toutefois que vous vérifiiez sur le serveur que le jeton d'authentification donné est autorisé pour l'opération (c'est-à-dire que si le jeton d'authentification est destiné à un utilisateur qui ne possède pas l'autorisation nécessaire sur cet objet particulier, il DOIT être rejeté).

Lorsqu'elle est gérée correctement, la collision ne pose pas vraiment de problème de sécurité (l'utilisateur ou le client sera simplement invité à relancer l'opération avec un autre UUID).

Lie Ryan
la source
C'est un très bon point. Peut-être que c'est tout ce qui est nécessaire et j'y réfléchis trop. Cependant, cette approche suscite quelques inquiétudes. Le plus important est que les uuids générés par javascript ne semblent pas être aussi aléatoires qu'on pourrait l'espérer (voir les commentaires à stackoverflow.com/a/2117523/13181 pour les détentions). Il semble que window.crypto devrait résoudre ce problème, mais cela ne semble pas être disponible dans toutes les versions de navigateur que je dois prendre en charge.
Herbrandson
suite ... J'aime votre suggestion d'ajouter du code dans le client qui permettrait de régénérer un nouvel uuid en cas de collision. Cependant, il me semble que cela réintroduit l'inquiétude que j'avais dans mon message au point 1 de la section "Quels sont les avantages de cette approche". Je pense que si je choisissais cette voie, je ferais mieux de générer des identifiants temporaires du côté client, puis de les mettre à jour avec le "identifiant final" du serveur une fois connecté
herbrandson le
suite à nouveau ... De plus, permettre aux utilisateurs de soumettre leurs propres identifiants uniques semble être un problème de sécurité. Peut-être que la taille d'un uuid et l'improbabilité statistique élevée d'une collision suffisent à atténuer ce problème en soi. Je ne suis pas sûr. Je soupçonne sans cesse que garder chaque utilisateur dans son propre "bac à sable" ne soit qu'une bonne idée dans ce cas (c.-à-d. Ne faites confiance à aucune entrée d'utilisateur).
Herbrandson
@herbrandson: Je ne peux imaginer aucun problème de sécurité en permettant aux utilisateurs de générer leur propre identifiant unique à condition de toujours vérifier que l'utilisateur dispose des autorisations nécessaires pour l'opération. ID est juste quelque chose qui peut être utilisé pour identifier des objets, peu importe sa valeur. Le seul inconvénient potentiel est que l'utilisateur puisse réserver une série d'identifiants pour son propre usage, mais cela ne pose aucun problème au système dans son ensemble, car il est tout aussi improbable que d'autres utilisateurs parviennent à ces valeurs.
Lie Ryan
Merci pour vos commentaires. Cela m'a vraiment obligé à clarifier ma pensée! Il y a une raison pour laquelle je me méfiais de votre approche et je l'avais oubliée en cours de route :). Ma peur est liée à la mauvaise RNG dans de nombreux navigateurs. Pour la génération uuid, on préférerait un RNG cryptographiquement fort. Ceci est utilisé par de nombreux navigateurs récents via window.crypto, mais pas par les navigateurs plus anciens. De ce fait, il est possible pour un utilisateur malveillant de déterminer le germe d'un autre utilisateur RNG et de connaître ainsi le prochain uuid à générer. C'est la partie qui semble pouvoir être attaquée.
Herbrandson