Comment gérer les mots de passe en toute sécurité avec BcryptsJS en JavaScript

Introduction

Protéger les mots de passe des sites web est une compétence essentielle que tout développeur devrait avoir. JavaScript offre une option pour assurer le stockage sûr et le traitement des mots de passe ou d’autres données sensibles en utilisant les algorithmes de hachage fournis par le module BcryptJS de JavaScript.

Dans ce tutoriel, vous apprendrez à utiliser BcryptJS et le hachage pour configurer un serveur express de base qui stockera les mots de passe sous forme de hachages dans une base de données au lieu de chaînes brutes, et les récupérera pour authentifier le mot de passe.

Prérequis

Pour continuer avec ce tutoriel, vous devez avoir la configuration suivante.

  1. Une version stable de Node.js installée sur votre ordinateur avec la version 12.x ou supérieure. Vous pouvez utiliser ce tutoriel DigitalOcean pour installer la dernière version de Node Js sur votre ordinateur.

  2. Vous devez savoir comment coder en JavaScript.

  3. _Vous devriez avoir Express JS installé sur votre ordinateur. Vous pouvez utiliser ce guide pour apprendre comment configurer un serveur Express.

  4. Enfin, vous aurez besoin de la base de données MongoDB Community ou Atlas pour compléter ce tutoriel. Vous pouvez l’installer en suivant l’un de ces guides DigitalOcean sur_ comment installer MongoDB.

Pourquoi utiliser BcryptJS ?

Bcrypt est un algorithme de hachage pour créer des hashages de mots de passe afin de les stocker en cas de violation de données. Cet algorithme de hachage avancé utilise des sels, ce qui le rend difficile à craquer par des attaques telles que la force brute.

BcryptJS est l’implémentation JavaScript de l’algorithme de hachage Bcrypt, vous permettant d’utiliser le chiffrement de hachage sans avoir à vous occuper de fonctions de hachage complexes. Certaines des raisons qui font de BcryptJS un excellent choix pour la sécurité des mots de passe sont les suivantes :

  1. Sécurité – BcryptJS implémente l’algorithme Bcrypt, un algorithme lent (ce qui est une bonne chose en matière de hachage) et nécessite une puissance de calcul intense. Cela rend la tâche ardue pour les attaquants de casser le hachage du mot de passe, assurant ainsi la sécurité des mots de passe même en cas de violation de données.

  2. Salage – BcryptJS gère la génération de sels aléatoires pour les mots de passe afin d’assurer la sécurité du stockage (nous en apprendrons davantage sur les hachages et les sels dans la section suivante). Les sels rendent le hachage d’un mot de passe relativement faible plus complexe, le rendant plus difficile à décrypter.

  3. Facilité d’utilisation – BcryptJS fournit aux développeurs JavaScript un outil de chiffrement de leurs mots de passe sans nécessiter une compréhension approfondie du hachage.

À l’étape suivante, nous apprendrons brièvement sur les hachages et les sels.

Comment fonctionne le hachage ?

Avant d’utiliser ces concepts dans vos projets, vous devez comprendre comment fonctionnent le hachage et le salage.

Le hachage

Le hachage consiste à convertir une simple chaîne de caractères ou un texte en une chaîne de caractères aléatoires (chiffrement). Cela permet de stocker et/ou de transmettre de manière sécurisée des données sensibles. Le hachage implique les étapes clés suivantes :

Entrée de données – Tout d’abord, les données qui peuvent être de n’importe quel type (binaire, caractère, décimal, etc.) sous forme de texte simple ou de chaîne de caractères sont stockées.

La fonction de hachage – La fonction de hachage est un algorithme mathématique qui prend en entrée les données et les convertit en un ensemble de caractères ou de codes de hachage. Les fonctions de hachage sont déterministes (elles produisent la même sortie pour la même entrée) et des fonctions à sens unique (cela signifie qu’il est presque impossible de rétro-ingénier le résultat des fonctions de hachage, c’est-à-dire un hachage en ses données d’entrée).

Résistance aux collisions – Cela signifie qu’une fonction de hachage est créée en gardant à l’esprit l’idée de la résistance aux collisions, c’est-à-dire que deux entrées différentes ne peuvent pas avoir la même sortie (code de hachage).

Authentification – Les fonctions de hachage sont déterministes, elles produisent donc le même hachage pour la même entrée. Ainsi, lors de l’authentification d’un mot de passe stocké sous forme de hachage, l’idée générale est que si le mot de passe à authentifier correspond au hachage stocké dans la base de données, le mot de passe est correct.

Sel

Étant donné que le hachage existe depuis des décennies, il y a eu des développements tels que les tables arc-en-ciel, qui contiennent des milliards d’entrées de données contenant des chaînes de données et leurs hachages respectifs basés sur différents algorithmes de hachage.

Maintenant, considérons une situation où un utilisateur créant un compte sur votre site Web crée un mot de passe faible. Ainsi, en cas de violation des données, un attaquant peut rechercher les hachages de vos utilisateurs et trouver la correspondance du hachage pour le compte utilisateur avec un mot de passe faible. Cela serait catastrophique dans les applications à haute sécurité. Pour éviter qu’une telle chose ne se produise, des sels sont utilisés.

Le salage est une couche de sécurité supplémentaire ajoutée aux hachages en ajoutant une chaîne aléatoire de caractères au hachage d’un mot de passe avant de le stocker dans une base de données. Ainsi, même si les données sont divulguées lors d’une violation, il sera difficile pour un attaquant de décrypter un hachage contenant un sel. Considérez l’exemple suivant:

  1. Password = ‘sammy’
  2. Hash = £%$^&£!23!3%!!
  3. Salt = 2vqw£4Df$%sdfk
  4. Hash + Salt = £%$^&£!23!3%!!2vqw£4Df$%sdfk

Comme nous pouvons le voir clairement, le mot de passe stocké avec du sel est moins susceptible d’être craqué en raison de la nature déterministe des hachages. Par conséquent, si un attaquant, par exemple, recherche cette chaîne de hachage+mot de passe dans une table arc-en-ciel, il n’obtiendra pas la chaîne de mot de passe réelle mais quelque chose de complètement différent.

Maintenant, vous êtes prêt à utiliser BcryptJS et à sécuriser vos mots de passe de manière conforme aux normes de l’industrie.

Installation de BcryptJS et d’autres modules requis

Maintenant que vous connaissez les concepts de hachage et de salage, il ne vous reste plus qu’à allumer votre ordinateur et commencer à coder. La structure du projet sera la suivante :

Project Structure

Tout d’abord, nous allons créer un projet npm avec les étapes suivantes :

  1. Ouvrez un dossier et créez un fichier app.js.

  2. Ouvrez une fenêtre de terminal dans ce dossier et saisissez la commande

npm init

Après cela, on vous demandera des entrées, mais vous pouvez appuyer sur Entrée sans fournir d’entrées.

  1. Ensuite, créez 3 autres fichiers, à savoir
  • auth.js
  • db.js
  • User.js
  1. Dans la même fenêtre de terminal, saisissez la commande suivante pour installer les packages nécessaires.
npm install express mongoose BcryptJS nodemon

Vous avez maintenant une configuration d’environnement de projet complet à suivre dans ce tutoriel. À l’étape suivante, vous apprendrez comment créer un serveur pour utiliser BcryptJS afin de stocker et d’authentifier de manière sécurisée les mots de passe avec MongoDB.

Configuration d’un serveur avec Express JS

Maintenant que vous avez configuré la structure du projet, vous pouvez créer un serveur qui utilise bcrytjs pour sécuriser les mots de passe en les stockant sous forme de hachages et les authentifier. Nous allons créer le serveur en suivant les étapes suivantes.

Étape 1 – Création d’une connexion à la base de données MongoDB

Pour se connecter à mongoDB, nous utilisons la version communautaire. Pour garder le projet organisé, vous enregistrerez le code de configuration d’une connexion dans le fichier db.js.

  1. const mongoose = require("mongoose");
  2. const mongoURI = "mongodb://127.0.0.1:27017/bcrypt_database";
  3. const connectMongo = async () => {
  4. try {
  5. await mongoose.connect(mongoURI);
  6. console.log("Connected to MongoDB!");
  7. } catch (error) {
  8. console.error("Error connecting to MongoDB: ", error.message);
  9. }
  10. };
  11. module.exports = connectMongo;

Ici, vous importez le package mongoose, qui fournit une API pour connecter JavaScript à MongoDB. De plus, dans ce cas, l’URI de connexion est pour une installation locale de MongoDB, si vous utilisez une base de données cloud (comme Atlas), vous devez simplement modifier l’URI pour l’URI spécifique de votre base de données.

Info
Une URI est similaire à une URL de serveur avec la différence qu’une URI peut identifier le nom et l’identité des ressources et leur emplacement sur Internet. En revanche, une URL est un sous-ensemble d’une URI capable de ne faire que la dernière.

La fonction connectToMongo est une fonction async car mongoose.connect renvoie une promesse JavaScript. Cette fonction renverra une connexion en cas d’exécution réussie. Sinon, elle renverra une erreur.

Enfin, nous utilisons module.exports pour exporter cette fonction chaque fois que le module db.js est importé.

Étape 2 – Création d’un schéma utilisateur

Vous aurez besoin d’un schéma de base pour créer ou authentifier des utilisateurs avec une base de données MongoDB. Si vous ne savez pas ce qu’est un schéma, vous pouvez utiliser ce excellent guide DO pour comprendre et créer le schéma dans MongoDB. Nous n’utiliserons que deux champs pour notre schéma, email et password.

Utilisez le code suivant dans votre module User.js.

  1. const mongoose = require("mongoose");
  2. const UserSchema = new mongoose.Schema({
  3. email:{
  4. type:String,
  5. required:true,
  6. unique:true
  7. },
  8. password:{
  9. type:String,
  10. required:true
  11. }
  12. });
  13. module.exports = mongoose.model('user', UserSchema)

Nous créons deux champs dans ce schéma, email et password. Les deux sont des champs obligatoires et de type de données chaîne de caractères. De plus, email est un champ unique, ce qui signifie qu’une adresse e-mail ne peut être utilisée qu’une seule fois pour créer un compte sur ce serveur.

Enfin, nous exportons un modèle appelé users. Un modèle représente un schéma côté base de données. Vous pouvez considérer un schéma comme une règle pour définir un modèle, tandis que le modèle est stocké comme une collection dans la base de données MongoDB.

Vous pouvez convertir un schéma en modèle en utilisant la fonction model() de la bibliothèque mongoose.

Étape 3 – Configuration du serveur dans app.js

Après avoir suivi les étapes précédentes, vous avez réussi à créer un modèle et un module pour établir une connexion à la base de données MongoDB. Maintenant, vous allez apprendre à configurer le serveur. Utilisez le code suivant dans votre fichier app.js.

  1. const connectToMongo = require("./db");
  2. const express = require("express");
  3. const app = express();
  4. connectToMongo();
  5. app.use(express.json());
  6. app.use("/auth", require("./auth"));
  7. const port = 3300;
  8. app.listen(port, () => {
  9. console.log(`Listening at http://localhost:${port}`);
  10. });

Ici, nous importons les modules express et db.js pour nous connecter à MongoDB. Ensuite, nous utilisons le middleware express.json() pour gérer les réponses JSON. Les routes sont créées dans un module différent (auth.js) pour maintenir le code propre et organisé. Enfin, nous créons un point de terminaison pour que le serveur écoute sur le port 3300 en local. (Vous pouvez utiliser n’importe quel port de votre choix)

Chiffrer les mots de passe et les stocker dans une base de données MongoDB

Jusqu’à présent, le serveur est presque prêt, et maintenant vous allez créer des points d’accès pour le serveur. Nous allons créer deux points d’accès – signup et login. Dans le point d’accès signup, nous prendrons l’email et le mot de passe d’un nouvel utilisateur et le stockerons avec un chiffrement pour un mot de passe en utilisant BcryptJS.

Dans le fichier auth.js, tapez le code suivant:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // ROUTE 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. module.exports = router;

Nous effectuons les imports nécessaires, puis nous configurons le routeur express pour créer le point d’accès /signup. Nous utilisons la méthode POST afin que les informations d’identification ne soient pas divulguées dans l’URL de l’application. Ensuite, nous créons un sel en utilisant la fonction genSalt du package scripts; le paramètre passé aux fonctions genSalt() contient la longueur des caractères de sel. Ensuite, nous utilisons la fonction hash() de BcryptJS, qui prend un paramètre obligatoire, la chaîne de mot de passe à convertir en code de hachage, et un argument facultatif, la chaîne de sel. Ensuite, elle renvoie un hachage qui contient à la fois le mot de passe et le sel.

Après cela, nous utilisons la fonction create() du module mongoose pour créer un document dans notre base de données défini par les règles du modèle users. Elle prend un objet Javascript contenant l’email et le mot de passe, mais au lieu de lui donner une chaîne brute, nous donnons le secPass (hash du mot de passe + sel) à stocker dans la base de données. De cette manière, nous stockons de manière sécurisée un mot de passe dans la base de données en utilisant son hash combiné avec un sel au lieu d’une chaîne brute. En fin de compte, nous renvoyons une réponse JSON contenant le modèle d’utilisateur. (Cette méthode d’envoi de réponses est uniquement pour la phase de développement ; en production, vous la remplacerez par un jeton d’authentification ou autre chose).

Pour tester ce point de terminaison, vous devez d’abord exécuter le serveur, ce qui peut être fait en tapant la commande suivante dans le terminal.

cd <path to your project folder>
nodemon ./app.js

Cette commande exécutera votre serveur sur localhost et le port 3300 (ou tout autre port que vous spécifiez). Ensuite, vous pouvez envoyer une requête HTTP à l’URL http://localhost:3300/auth/signup avec le corps suivant:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

Cela produira la sortie/réponse suivante:

{
  "user": {
    "email": "[email protected]",
    "password": "$2a$10$JBka/WyJD0ohkzyu5Wu.JeCqQm33UIx/1xqIeNJ1AQI9kYZ0Gr0IS",
    "_id": "654510cd8f1edaa59a8bb589",
    "__v": 0
  }
}

Note: Le hash du mot de passe et l’ID ne seront pas les mêmes, car ils sont toujours uniques.

Dans la section suivante, vous apprendrez comment accéder au hash stocké pour le mot de passe et l’authentifier avec un mot de passe fourni lors de la connexion/authentification.

Accéder au mot de passe chiffré et l’utiliser pour l’authentification

Jusqu’à présent, vous avez appris à utiliser BcryptJS, le chiffrement, le salage et à développer un serveur Express qui crée de nouveaux utilisateurs dont les mots de passe sont stockés sous forme de hachages. Maintenant, vous allez apprendre à utiliser le mot de passe stocké pour authentifier un utilisateur lorsqu’il essaie de se connecter à l’application.

Pour ajouter une méthode d’authentification, ajoutez la route suivante dans votre fichier auth.js après la route /signup:

  1. // ROUTE 2:
  2. router.post("/login", async (req, res) => {
  3. let user = await User.findOne({ email: req.body.email });
  4. if (!user) {
  5. return res.status(400).json({ error: "Login with proper credentials!" });
  6. }
  7. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  8. if (!passwordCompare) {
  9. return res
  10. .status(400)
  11. .json({ error: "Login with proper credentials!" });
  12. }
  13. res.json({ success: "Authenticated!" });
  14. });

Ici, nous utilisons le sous-chemin /auth/login pour effectuer une authentification de connexion pour un utilisateur déjà existant. Tout comme l’endpoint /auth/signup, il s’agira d’une fonction async-await, car BcryptJS renvoie des promesses.

Tout d’abord, nous utilisons la fonction findOne de la bibliothèque Mongoose, qui est utilisée pour trouver un document dans une collection en fonction d’une requête de recherche donnée. Dans cette instance, nous recherchons l’utilisateur en fonction de son adresse e-mail. Si aucun utilisateur avec l’e-mail fourni n’existe, cela renverra une réponse avec le code d’état 400 pour des identifiants invalides. (Il n’est pas conseillé de fournir quel paramètre est incorrect lors de la connexion, car les attaquants peuvent utiliser ces informations pour trouver des comptes existants).

Si l’utilisateur avec l’email fourni existe, le programme passe à la comparaison des mots de passe. À cette fin, BcryptJS fournit la méthode compare(), qui prend une chaîne brute en premier argument et un hash (avec ou sans salt) en deuxième argument. Elle renvoie ensuite une promesse booléenne : true si le mot de passe correspond au hash et false sinon. Ensuite, vous pouvez ajouter une simple vérification en utilisant une instruction if et renvoyer un succès ou une erreur en fonction de la comparaison.

Enfin, vous exporterez le routeur express en utilisant module.exports pour que le point de départ app.js puisse l’utiliser pour les routes.

Pour tester cette route, vous pouvez envoyer une autre réponse HTTP à cette URL http://localhost:3300/auth/login avec le corps suivant:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

Cette requête donnera la réponse suivante:

{
  "success": "Authenticated!"
}

Finalement, auth.js ressemblera à ceci:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // ROUTE 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. // ROUTE 2:
  16. router.post("/login", async (req, res) => {
  17. let user = await User.findOne({ email: req.body.email });
  18. if (!user) {
  19. return res.status(400).json({ error: "Login with proper credentials!" });
  20. }
  21. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  22. if (!passwordCompare) {
  23. return res
  24. .status(400)
  25. .json({ error: "Login with proper credentials!" });
  26. }
  27. res.json({ success: "Authenticated!" });
  28. });
  29. module.exports = router;

Conclusion

Ce tutoriel a créé un serveur pour expliquer l’utilisation de BcryptJS pour stocker et accéder de manière sécurisée aux mots de passe de la base de données. Vous pouvez aller plus loin en :

  • Mettant en œuvre d’autres routes pour d’autres tâches telles que la récupération des informations d’identification de l’utilisateur, la mise à jour des informations d’identification de l’utilisateur, etc.

  • Mise en œuvre de jetons d’authentification à envoyer en tant que réponses, etc.

Cela marque le début de votre parcours pour gérer les mots de passe en toute sécurité ; vous pouvez toujours ajouter plus de sécurité avec différentes techniques et technologies, vous permettant de créer des applications plus sûres et résilientes.

Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-passwords-safely-with-bcryptsjs-in-javascript