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
- Les identifiants doivent être globalement uniques (ou du moins uniques dans le système)
- Généré sur le client (c.-à-d. Via JavaScript dans le navigateur)
- Sécurisé (comme indiqué ci-dessus et autrement)
- Les données peuvent être visualisées / éditées par plusieurs utilisateurs, y compris ceux qui ne l'ont pas créé
- 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:
- Identifiez cela comme une action "insertion" (c.-à-d. Pas une mise à jour ou une suppression)
- Validez que les deux parties de la clé composite sont valides
- 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).
- 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?
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.
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.
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
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 )
Y a-t-il des lacunes qui me manquent qui pourraient permettre à un utilisateur malveillant de compromettre le système?
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 )
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.
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.
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.