Wie man Passwörter sicher mit BcryptsJS in JavaScript behandelt

Einführung

Den Schutz von Website-Passwörtern zu gewährleisten, ist eine essentielle Fähigkeit, die jeder Entwickler haben sollte. JavaScript bietet eine Option, um die sichere Speicherung und Verarbeitung von Passwörtern oder anderen sensiblen Daten mithilfe der Hashing-Algorithmen des BcryptJS-Moduls von JavaScript zu gewährleisten.

In diesem Tutorial lernen Sie mehr über BcryptJS und das Hashing, um einen einfachen Express-Server einzurichten, der Passwörter als Hashes anstatt als Klartext in einer Datenbank speichert und sie zur Authentifizierung des Passworts abruft.

Voraussetzungen

Um dieses Tutorial fortzusetzen, sollten Sie folgende Voraussetzungen erfüllen.

  1. Eine stabile Version von Node.js auf Ihrem Computer installiert, mit der Version 12.x oder höher. Sie können diese DigitalOcean-Anleitung verwenden, um die neueste Version von Node.js auf Ihrem Computer zu installieren.

  2. Sie sollten wissen, wie man in JavaScript programmiert.

  3. _Du solltest Express JS auf deinem Computer installiert haben. Du kannst diese Anleitung verwenden, um zu lernen, wie man einen Express-Server einrichtet.

  4. Zuletzt benötigst du die MongoDB Community oder Atlas-Datenbank, um dieses Tutorial abzuschließen. Du kannst es mithilfe einer dieser DigitalOcean-Anleitungen zur_ Installation von MongoDB installieren.

Warum BcryptJS verwenden?

Bcrypt ist ein Hashing-Algorithmus zur Erstellung von Hashes für Passwörter, um sie im Falle eines Datenlecks zu speichern. Dieser fortschrittliche Hashing-Algorithmus verwendet Salze, wodurch er schwer zu knacken ist, z. B. durch Brute-Force-Angriffe.

BcryptJS ist die JavaScript-Implementierung des Bcrypt-Hashing-Algorithmus, der es Ihnen ermöglicht, die Hash-Verschlüsselung zu verwenden, ohne sich mit komplexen Hashing-Funktionen befassen zu müssen. Einige der Gründe, die BcryptJS zu einer ausgezeichneten Wahl für die Passwortsicherheit machen, sind folgende:

  1. Sicherheit – BcryptJS implementiert den Bcrypt-Algorithmus, einen langsamen Algorithmus (was beim Hashing eine gute Sache ist), der eine intensive Rechenleistung erfordert. Dies erschwert es Angreifern, den Passworthash zu knacken, und gewährleistet die Sicherheit von Passwörtern auch im Falle eines Datenlecks._

  2. Salzen – BcryptJS übernimmt die Generierung zufälliger Salze für Passwörter, um die Speichersicherheit zu gewährleisten (Wir werden in dem nächsten Abschnitt mehr über Hashes und Salze erfahren). Salze machen den Hash eines relativ schwachen Passworts komplexer und erschweren das Entschlüsseln.

  3. Einfache Verwendung – BcryptJS bietet JavaScript-Entwicklern ein Tool zum Verschlüsseln ihrer Passwörter, ohne dass sie ein tiefgreifendes Verständnis von Hashing benötigen._

In dem nächsten Schritt werden wir kurz über Hashes und Salts lernen.

Wie funktioniert das Hashing?

Bevor Sie diese Konzepte in Ihren Projekten verwenden, müssen Sie verstehen, wie Hashing und Salting funktionieren.

Hashing

Hashing wandelt einen einfachen String oder Klartext in eine Zeichenkette zufälliger Zeichen (Verschlüsselung) um. Dadurch wird eine sichere Speicherung und/oder Übertragung sensibler Daten ermöglicht. Hashing umfasst die folgenden Schlüsselschritte:

Eingabe von Daten – Zunächst werden Daten, die beliebigen Typs sein können (binär, Zeichen, dezimal, etc.), als Klartext oder String gespeichert.

Die Hash-Funktion – Die Hash-Funktion ist ein mathematischer Algorithmus, der Eingaben von den Daten entgegennimmt und sie in eine Zeichenkette oder Hash-Codes umwandelt. Hash-Funktionen sind deterministisch (sie produzieren für dieselbe Eingabe dieselbe Ausgabe) und Einweg-Funktionen (Das bedeutet, dass es nahezu unmöglich ist, die Ausgabe von Hash-Funktionen, also einen Hash, in seine Eingabedaten zurückzuentwickeln).

Kollisionsresistenz – Dies bedeutet, dass eine Hash-Funktion unter Berücksichtigung der Kollisionsresistenz erstellt wird, d.h. zwei unterschiedliche Eingaben können nicht den gleichen Ausgabewert (Hash-Code) haben.

Authentifizierung – Die Hash-Funktionen sind deterministisch und erzeugen für dieselbe Eingabe immer denselben Hash-Wert. Bei der Authentifizierung eines als Hash gespeicherten Passworts geht man daher im Allgemeinen davon aus, dass das zu authentifizierende Passwort mit dem in der Datenbank gespeicherten Hash-Wert übereinstimmen muss, um korrekt zu sein.

Salzen

Da Hashing seit Jahrzehnten existiert, gab es Entwicklungen wie Regenbogentabellen, die Milliarden von Datensätzen mit Datenzeichenfolgen und den entsprechenden Hashes auf der Grundlage verschiedener Hashing-Algorithmen enthalten.

Stellen Sie sich jetzt eine Situation vor, in der ein Benutzer beim Erstellen eines Kontos auf Ihrer Website ein schwaches Passwort wählt. Bei einem Datenleck kann ein Angreifer die Hashes Ihrer Benutzer nachschlagen und den Hash für das Benutzerkonto mit einem schwachen Passwort finden. Dies wäre in hochsicherheitsrelevanten Anwendungen katastrophal. Um dies zu verhindern, werden Salze verwendet.

Salzen ist eine zusätzliche Sicherheitsebene, die Hashes durch Hinzufügen einer zufälligen Zeichenfolge zum Hash eines Passworts vor der Speicherung in einer Datenbank absichert. Selbst wenn die Daten bei einem Datenleck veröffentlicht werden, wird es für einen Angreifer schwer sein, einen mit Salz versehenen Hash zu entschlüsseln. Betrachten Sie das folgende Beispiel:

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

Wie wir klar erkennen können, ist das Passwort, das mit einem Salt gespeichert wird, aufgrund der deterministischen Natur von Hashes unwahrscheinlicher zu knacken. Wenn ein Angreifer zum Beispiel diese Hash+Passwort-Zeichenfolge in einer Rainbow-Tabelle nachschlägt, erhält er nicht die tatsächliche Passwort-Zeichenfolge, sondern etwas völlig anderes.

Jetzt bist du bereit, BcryptJS zu verwenden und deine Passwörter auf industrieübliche Weise zu sichern.

Installieren von BcryptJS und anderen erforderlichen Modulen

Jetzt, da du über das Hashing und Salting Bescheid weißt, bleibt dir nur noch, deinen Computer zu nehmen und mit dem Programmieren zu beginnen. Die Projektstruktur wird wie folgt aussehen:

Project Structure

Zuerst erstellen wir ein npm-Projekt mit den folgenden Schritten:

  1. Öffne einen Ordner und erstelle eine Datei namens app.js.

  2. Öffne ein Terminalfenster in diesem Ordner und gib den Befehl ein

npm init

Danach wirst du nach Eingaben gefragt, aber du kannst einfach Enter drücken, ohne Eingaben bereitzustellen.

  1. Dann erstelle 3 weitere Dateien, nämlich
  • auth.js
  • db.js
  • User.js
  1. In demselben Terminalfenster gib den folgenden Befehl ein, um die erforderlichen Pakete zu installieren.
npm install express mongoose BcryptJS nodemon

Sie haben jetzt eine vollständige Projektmittelumgebung eingerichtet, der Sie in diesem Tutorial folgen können. Im nächsten Schritt erfahren Sie, wie Sie einen Server erstellen, um BcryptJS zur sicheren Speicherung und Authentifizierung von Passwörtern mit MongoDB zu verwenden.

Einrichten eines Servers mit Express JS

Jetzt, da Sie die Projektstruktur eingerichtet haben, können Sie einen Server erstellen, der bcrytjs verwendet, um Passwörter sicher zu speichern und zu authentifizieren. Wir werden den Server mit den folgenden Schritten erstellen.

Schritt 1 – Eine Verbindung zur MongoDB-Datenbank herstellen

Um eine Verbindung zur mongoDB herzustellen, verwenden wir die Community Edition. Um das Projekt organisiert zu halten, speichern Sie den Code für die Einrichtung einer Verbindung in der Datei 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;

Hier importieren Sie das Paket mongoose, das eine API zum Verbinden von JavaScript mit MongoDB bereitstellt. In diesem Fall handelt es sich bei der Verbindungs-URI um eine lokale Installation von MongoDB. Wenn Sie eine Cloud-Datenbank verwenden (wie Atlas), müssen Sie die URI nur auf die spezifische URI Ihrer Datenbank ändern.

Info
Eine URI ähnelt einer Server-URL mit dem Unterschied, dass eine URI den Namen und die Identität der Ressourcen und deren Standort im Internet identifizieren kann. Im Gegensatz dazu ist eine URL eine Teilmenge einer URI, die nur letzteres tun kann.

Die Funktion connectToMongo ist eine async Funktion, weil mongoose.connect ein Javascript-Versprechen zurückgibt. Diese Funktion gibt eine Verbindung bei erfolgreicher Ausführung zurück. Andernfalls wird ein Fehler zurückgegeben.

Zuletzt verwenden wir module.exports, um diese Funktion zu exportieren, wenn das Modul db.js importiert wird.

Schritt 2 – Erstellen eines Benutzerschemas

Sie benötigen ein grundlegendes Schema, um Benutzer mit einer MongoDB-Datenbank zu erstellen oder zu authentifizieren. Wenn Sie nicht wissen, was ein Schema ist, können Sie diesen ausgezeichneten DO-Leitfaden verwenden, um das Schema in MongoDB zu verstehen und zu erstellen. Wir werden nur zwei Felder für unser Schema verwenden, E-Mail und Passwort.

Verwenden Sie den folgenden Code in Ihrem Modul 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)

Wir erstellen zwei Felder in diesem Schema, email und password. Beide sind erforderliche Felder vom Datentyp String. Außerdem ist email ein eindeutiges Feld, das bedeutet, dass eine E-Mail-Adresse nur einmal verwendet werden kann, um ein Konto auf diesem Server zu haben.

Zum Schluss exportieren wir ein Modell namens users. Ein Modell repräsentiert ein Schema auf der Datenbankseite. Man kann sich ein Schema als eine Regel zum Definieren eines Modells vorstellen, während das Modell als eine Sammlung in der MongoDB-Datenbank gespeichert wird.

Man kann ein Schema mit der model()-Funktion der mongoose-Bibliothek in ein Modell umwandeln.

Schritt 3 – Einrichten des Servers in app.js

Nachdem Sie die vorherigen Schritte befolgt haben, haben Sie erfolgreich ein Modell und ein Modul erstellt, um eine Verbindung zur MongoDB-Datenbank herzustellen. Jetzt lernen Sie, den Server einzurichten. Verwenden Sie den folgenden Code in Ihrer app.js-Datei.

  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. });

Hier importieren wir die Module express und db.js, um eine Verbindung zur MongoDB herzustellen. Dann verwenden wir das Middleware express.json(), um JSON-Antworten zu verarbeiten. Die Routen werden in einem separaten Modul (auth.js) erstellt, um den Code sauber und organisiert zu halten. Schließlich erstellen wir einen Endpunkt, auf dem der Server auf Port 3300 auf localhost lauscht. (Sie können jeden beliebigen Port verwenden)

Passwörter verschlüsseln und in einer MongoDB-Datenbank speichern

Bis zu diesem Punkt ist der Server fast fertig und jetzt werden Sie Endpunkte für den Server erstellen. Wir werden zwei Endpunkte erstellen – signup und login. Im Signup-Endpunkt nehmen wir die E-Mail und das Passwort eines neuen Benutzers entgegen und speichern es verschlüsselt für ein Passwort mit Hilfe von BcryptJS.

In der Datei auth.js geben Sie den folgenden Code ein:

  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;

Wir importieren die erforderlichen Module und richten dann den Express Router ein, um den Endpunkt /signup zu erstellen. Wir verwenden die Methode POST, damit die Anmeldedaten nicht in der URL der Anwendung angezeigt werden. Danach erstellen wir ein Salt mit der Funktion genSalt des Pakets scripts; der Parameter, der an die Funktion genSalt() übergeben wird, enthält die Länge des Salt-Zeichens. Dann verwenden wir die Funktion hash() von BcryptJS, die einen erforderlichen Parameter, den zu hashenden Passwort-String, und ein optionales Argument, den Salt-String, annimmt. Danach gibt sie einen Hash zurück, der sowohl das Passwort als auch das Salt enthält.

Nachdem dies geschehen ist, verwenden wir die Funktion create() des Moduls mongoose, um ein Dokument in unserer Datenbank zu erstellen, das durch die Regeln des Modells users definiert ist. Es wird ein Javascript-Objekt benötigt, das E-Mail und Passwort enthält. Anstatt ihm einen einfachen String zu übergeben, geben wir jedoch secPass (Passworthash + Salt) weiter, um es in der Datenbank zu speichern. Auf diese Weise haben wir ein Passwort sicher in der Datenbank gespeichert, indem wir seinen Hash zusammen mit einem Salt verwenden, anstatt einen einfachen String zu verwenden. Schließlich geben wir eine JSON-Antwort zurück, die das Benutzermodell enthält. (Diese Methode, um Antworten zu senden, gilt nur für die Entwicklungsphase; in der Produktion ersetzen Sie dies durch einen Authentifizierungstoken oder etwas Ähnliches).

Um diesen Endpunkt zu testen, müssen Sie zuerst den Server starten, was durch Eingabe des folgenden Befehls in das Terminal erfolgt:

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

Dieser Befehl führt Ihren Server auf localhost und Port 3300 (oder einem anderen gewünschten Port) aus. Anschließend können Sie eine HTTP-Anfrage an die URL http://localhost:3300/auth/signup mit dem folgenden Body senden:

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

Dies erzeugt die folgende Ausgabe/Antwort:

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

Hinweis: Der Passworthash und die ID werden nicht gleich sein, da sie immer eindeutig sind.

In dem folgenden Abschnitt erfahren Sie, wie Sie auf den gespeicherten Hash für das Passwort zugreifen und ihn mit einem beim Login/Authentifizierung bereitgestellten Passwort authentifizieren können.

Zugriff auf das verschlüsselte Passwort und Verwendung zur Authentifizierung

Bisher haben Sie gelernt, wie man BcryptJS, Hashing, Salting verwendet und einen Express-Server entwickelt, der neue Benutzer mit Passwörtern als Hashes speichert. Jetzt lernen Sie, wie Sie das gespeicherte Passwort verwenden und einen Benutzer authentifizieren können, wenn er versucht, sich bei der Anwendung anzumelden.

Fügen Sie eine Authentifizierungsmethode hinzu, indem Sie den folgenden Routenabschnitt in Ihrer auth.js nach der /signup-Route hinzufügen:

  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. });

Hier verwenden wir den Unterpfad /auth/login, um die Anmeldung für einen bereits vorhandenen Benutzer zu authentifizieren. Ähnlich wie der /auth/signup-Endpunkt handelt es sich hierbei um eine asynchrone Funktion mit await, da BcryptJS Promises zurückgibt.

Zunächst verwenden wir die Funktion findOne der Mongoose-Bibliothek, um ein Dokument in einer Sammlung anhand einer bestimmten Suchanfrage zu finden. In diesem Fall suchen wir den Benutzer anhand der E-Mail. Wenn kein Benutzer mit der angegebenen E-Mail vorhanden ist, wird eine Antwort mit dem Statuscode 400 für ungültige Anmeldeinformationen gesendet. (Es ist keine gute Praxis, während der Anmeldung anzugeben, welcher Parameter falsch ist, da Angreifer diese Informationen verwenden können, um vorhandene Konten zu finden).

Wenn der Benutzer mit der angegebenen E-Mail existiert, geht das Programm zur Passwortüberprüfung über. Hierfür bietet BcryptJS die Methode compare() an, die einen Rohstring als ersten Argument und einen Hash (mit oder ohne Salt) als zweites Argument annimmt. Sie gibt dann ein Boolean Promise zurück; true, wenn das Passwort mit dem Hash übereinstimmt, und false, wenn sie nicht übereinstimmen. Anschließend können Sie eine einfache Überprüfung mit einer if-Anweisung durchführen und je nach Vergleichsergebnis Erfolg oder Fehler zurückgeben.

Zum Schluss exportieren Sie den Express Router mit module.exports, damit er von dem Ausgangspunkt app.js für die Routen verwendet werden kann.

Um diese Route zu testen, können Sie eine weitere HTTP-Antwort an die URL http://localhost:3300/auth/login mit dem folgenden Body senden:

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

Diese Anfrage gibt die folgende Antwort zurück:

{
  "success": "Authenticated!"
}

Zum Schluss wird auth.js wie folgt aussehen:

  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;

Fazit

In diesem Tutorial wurde ein Server erstellt, um die Verwendung von BcryptJS zur sicheren Speicherung und zum Zugriff auf Passwörter in einer Datenbank zu erläutern. Sie können dies weiter ausbauen, indem Sie:

  • Weitere Routen für andere Aufgaben wie das Abrufen von Benutzeranmeldeinformationen, das Aktualisieren von Benutzeranmeldeinformationen usw. implementieren.

  • Implementieren von Authentifizierungstokens, die als Antworten gesendet werden, usw.

Dies ist der Beginn Ihrer Reise, um Passwörter sicher zu behandeln. Sie können jederzeit weitere Sicherheitstechniken und -technologien hinzufügen, um sicherere und widerstandsfähigere Anwendungen zu erstellen.

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