Hoe wachtwoorden veilig te behandelen met BcryptsJS in JavaScript

Introductie

Het beschermen van website wachtwoorden is een essentiële vaardigheid die elke ontwikkelaar zou moeten hebben. JavaScript biedt een optie om ervoor te zorgen dat wachtwoorden of andere gevoelige gegevens veilig worden opgeslagen en verwerkt met behulp van de hashalgoritmen die worden geleverd door de BcryptJS-module van JavaScript.

In deze zelfstudie leer je over BcryptJS en hashen om een eenvoudige express-server op te zetten die wachtwoorden opslaat als hashes in een database in plaats van als platte tekst en ze ophaalt om het wachtwoord te authenticeren.

Vereisten

Om door te gaan met deze zelfstudie, moet je de volgende setup hebben.

  1. Een stabiele versie van Node.js geïnstalleerd op je computer met versie 12.x of hoger. Je kunt deze DigitalOcean-zelfstudie gebruiken om de nieuwste Node.js-versie op je computer te installeren.

  2. Je moet weten hoe je in JavaScript moet coderen.

  3. _Je zou Express JS geïnstalleerd moeten hebben op je computer. Je kunt deze handleiding gebruiken om te leren hoe je een Express-server opzet.

  4. Tenslotte heb je de MongoDB-community of Atlas-database nodig om deze tutorial te voltooien. Je kunt het installeren met een van deze DigitalOcean-handleidingen over_ hoe MongoDB te installeren.

Waarom BcryptJS gebruiken?

Bcrypt is een hashalgoritme om hashes voor wachtwoorden te maken en ze op te slaan in geval van een datalek. Dit geavanceerde hashalgoritme maakt gebruik van zouten, waardoor het moeilijk te kraken is door aanvallen zoals brute-force.

BcryptJS is de JavaScript-implementatie van het Bcrypt-hashalgoritme, waarmee je hash-encryptie kunt gebruiken zonder te hoeven knoeien met complexe hashfuncties. Enkele redenen waarom BcryptJS een uitstekende keuze is voor wachtwoordbeveiliging zijn de volgende:

  1. Veiligheid – BcryptJS implementeert het Bcrypt-algoritme, een traag algoritme (wat in hashen een goede zaak is) en vereist intense rekenkracht. Dit maakt het een lastige taak voor aanvallers om het wachtwoordhash te kraken, waardoor de veiligheid van wachtwoorden wordt gegarandeerd, zelfs bij een datalek._

  2. Zouting – BcryptJS behandelt de generatie van willekeurige zouten voor wachtwoorden om opslagbeveiliging te garanderen (we zullen in de volgende sectie meer in detail leren over hashes en zouten). Zouten maken de hash van een relatief zwak wachtwoord complexer, waardoor het moeilijker wordt om te decoderen.

  3. Gemakkelijk te Gebruiken – BcryptJS biedt JavaScript-ontwikkelaars een tool om hun wachtwoorden te versleutelen zonder dat ze diepgaande kennis van hashen nodig hebben._

In de volgende stap zullen we kort iets leren over hashes en salts.

Hoe werkt hashen?

Voordat u deze concepten in uw projecten gebruikt, moet u begrijpen hoe hashen en salten werken.

Hashen

Hashen is het omzetten van een eenvoudige tekenreeks of platte tekst in een tekenreeks van willekeurige tekens (versleuteling). Dit maakt veilige opslag en/of verzending van gevoelige gegevens mogelijk. Hashen omvat de volgende belangrijke stappen:

Data-invoer – Allereerst wordt data die van elk type kan zijn (binair, karakter, decimaal, enz.) als platte tekst of tekenreeks opgeslagen.

De hashfunctie – De hashfunctie is een wiskundig algoritme dat invoer vanuit de data neemt en het omzet in een set tekens of hashcodes. Hashfuncties zijn deterministisch (produceren dezelfde uitvoer voor dezelfde invoer) en éénrichtingsfuncties (Dit betekent dat het bijna onmogelijk is om de uitvoer van hashfuncties om te keren, d.w.z. een hash terug naar zijn invoergegevens om te zetten).

Weerstand tegen botsingen – Dit betekent dat een hash-functie wordt gemaakt met het idee van weerstand tegen botsingen in gedachten, d.w.z., twee verschillende invoeren kunnen niet dezelfde uitvoer (hash-code) hebben.

Authenticatie – De hash-functies zijn deterministisch en produceren dezelfde hash voor dezelfde invoer. Dus, bij het authenticeren van een wachtwoord dat is opgeslagen als een hash, is het algemene idee dat als het te authenticeren wachtwoord overeenkomt met de hash die in de database is opgeslagen, het wachtwoord correct is.

Zouten

Aangezien hashen al tientallen jaren bestaat, zijn er ontwikkelingen geweest zoals rainbow tables, die miljarden gegevensvermeldingen bevatten met gegevenstroken en hun respectievelijke hashes op basis van verschillende hash-algoritmen.

Stel je nu een situatie voor waarin een gebruiker een account op jouw website aanmaakt met een zwak wachtwoord. In geval van een datalek kan een aanvaller de hashes van jouw gebruikers opzoeken en de overeenkomst van de hash voor het gebruikersaccount met een zwak wachtwoord vinden. Dit zou rampzalig zijn in toepassingen met een hoge beveiliging. Om te voorkomen dat zoiets gebeurt, worden zouten gebruikt.

Zouten is een extra beveiligingslaag toegevoegd aan hashes door een willekeurige reeks tekens toe te voegen aan de hash van een wachtwoord voordat deze in een database wordt opgeslagen. Zelfs als de gegevens worden gelekt bij een inbreuk, zal het moeilijk zijn voor een aanvaller om een hash met zout te ontcijferen. Overweeg het volgende voorbeeld:

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

Zoals we duidelijk kunnen zien, is het wachtwoord dat met zout is opgeslagen minder waarschijnlijk te kraken vanwege de deterministische aard van hashes. Daarom zal een aanvaller, bijvoorbeeld, als hij deze hash+wachtwoordreeks opzoekt in een rainbow table, niet het daadwerkelijke wachtwoord krijgen, maar iets compleet anders.

Nu ben je klaar om BcryptJS te gebruiken en je wachtwoorden op een branchestandaard manier te beveiligen.

Het installeren van BcryptJS en andere benodigde modules

Nu je op de hoogte bent van hashing en salting, hoef je alleen nog maar je computer te pakken en te beginnen met coderen. De projectstructuur zal als volgt zijn:

Project Structure

Als eerste beginnen we met het aanmaken van een npm-project met de volgende stappen:

  1. Open een map en maak een bestand aan, app.js.

  2. Open een terminalvenster in deze map en typ het commando

npm init

Hierna wordt er om invoer gevraagd, maar je kunt op Enter drukken zonder invoer te geven.

  1. Vervolgens maak je 3 extra bestanden aan, namelijk
  • auth.js
  • db.js
  • User.js
  1. In hetzelfde terminalvenster typ je het volgende commando om de benodigde pakketten te installeren.
npm install express mongoose BcryptJS nodemon

Je hebt nu een volledige projectomgeving ingesteld om te volgen in deze tutorial. In de volgende stap leer je hoe je een server maakt om BcryptJS te gebruiken voor het veilig opslaan en authenticeren van wachtwoorden met MongoDB.

Het opzetten van een server met Express JS

Nu je de projectstructuur hebt ingesteld, kun je een server maken die bcryptjs gebruikt om wachtwoorden veilig op te slaan door ze als hashes op te slaan en te authenticeren. We zullen de server maken met de volgende juiste stappen.

Stap 1 – Het maken van een verbinding met de MongoDB-database

Om verbinding te maken met MongoDB, gebruiken we de community-editie. Om het project georganiseerd te houden, sla je de code voor het opzetten van een verbinding op in het bestand 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 importeer je het mongoose-pakket, dat een API biedt om JavaScript met MongoDB te verbinden. Ook is in dit geval de verbindings-URI voor een lokale installatie van MongoDB, als je een clouddatabase gebruikt (zoals Atlas), hoef je alleen de URI te wijzigen naar de specifieke URI van jouw database.

Informatie
Een URI lijkt op een server-URL met als verschil dat een URI de naam en identiteit van de bronnen en hun locatie op het internet kan identificeren. Daarentegen is een URL een subset van een URI die alleen het laatste kan doen.

De connectToMongo-functie is een async-functie omdat mongoose.connect een JavaScript-promise retourneert. Deze functie zal een verbinding geven bij succesvolle uitvoering. Anders retourneert het een foutmelding.

Tenslotte gebruiken we module.exports om deze functie te exporteren wanneer het db.js-module wordt geïmporteerd.

Stap 2 – Het maken van een Gebruikersschema

U heeft een basis-schema nodig om gebruikers te maken of te authenticeren met een MongoDB-database. Als u niet weet wat een schema is, kunt u deze uitstekende DO-gids gebruiken om het schema in MongoDB te begrijpen en te maken. We zullen slechts twee velden gebruiken voor ons schema, email en wachtwoord.

Gebruik de volgende code in uw User.js-module.

  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)

We maken twee velden aan in dit schema, email en wachtwoord. Beide zijn verplichte velden en van het gegevenstype string. Ook is email een uniek veld, wat betekent dat een e-mail slechts één keer kan worden gebruikt om een ​​account op deze server te hebben.

Tenslotte exporteren we een model genaamd gebruikers. Een model vertegenwoordigt een schema aan de databasekant. Je kunt een schema zien als een regel voor het definiëren van een model, terwijl het model wordt opgeslagen als een verzameling in de MongoDB-database.

Je kunt een schema omzetten naar een model met behulp van de model()-functie van de mongoose-bibliotheek.

Stap 3 – Het instellen van de server in app.js

Na het volgen van de vorige stappen, heb je met succes een model en een module aangemaakt om een verbinding te maken met de MongoDB-database. Nu leer je de server op te zetten. Gebruik de volgende code in je app.js-bestand.

  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 importeren we de modules express en db.js om verbinding te maken met MongoDB. Vervolgens gebruiken we de express.json()-middleware voor het afhandelen van JSON-responsen. De routes worden aangemaakt in een andere module (auth.js) om de code schoon en georganiseerd te houden. Daarna creëren we een eindpunt voor de server om te luisteren op poort 3300 op localhost. (Je kunt elke poort naar keuze gebruiken)

Wachtwoorden versleutelen en opslaan in een MongoDB-database

Op dit punt is de server bijna klaar, en nu ga je endpoints voor de server maken. We zullen twee endpoints maken – signup en login. In het signup-eindpunt nemen we het e-mailadres en wachtwoord van een nieuwe gebruiker en slaan het op met versleuteling voor een wachtwoord met behulp van BcryptJS.

In het bestand auth.js, typ de volgende code:

  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;

We maken de nodige imports en stellen vervolgens de express-router in om het /signup-eindpunt te maken. We gebruiken de POST-methode zodat de referenties niet worden onthuld in de URL in de applicatie. Daarna maken we een Salt met behulp van de genSalt-functie van het scripts-pakket; het parameter doorgegeven aan de genSalt()-functies bevat de lengte van het zoutkarakter. Vervolgens gebruiken we de hash()-functie van BcryptJS, die een verplichte parameter aanneemt, de te converteren wachtwoordstring naar hashcode, en een optioneel argument, de zoutstring. En dan retourneert het een hash die zowel het wachtwoord als het zout bevat.

Na dit, gebruiken we de functie create() van de mongoose-module om een document in onze database te maken volgens de regels van het gebruikers-model. Het neemt een Javascript-object met e-mail en wachtwoord, maar in plaats van een ruwe tekenreeks te geven, geven we de secPass (wachtwoord-hash + zout) om in de database te worden opgeslagen. Op deze manier hebben we veilig een wachtwoord in de database opgeslagen met zijn hash gecombineerd met een zout in plaats van een ruwe tekenreeks. Uiteindelijk retourneren we een JSON-respons met het gebruikersmodel. (Deze methode voor het verzenden van reacties is alleen voor de ontwikkelingsfase; in productie vervang je dit door een verificatietoken of iets anders).

Om deze eindpunt te testen, moet je eerst de server uitvoeren, wat kan door de volgende opdracht in de terminal in te voeren.

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

Deze opdracht zal je server uitvoeren op localhost en poort 3300 (of welke poort je ook opgeeft). Vervolgens kun je een HTTP-verzoek sturen naar de URL http://localhost:3300/auth/signup met de volgende body:

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

Dit zal de volgende uitvoer/respons opleveren:

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

Let op: De wachtwoord-hash en ID zullen niet hetzelfde zijn, omdat ze altijd uniek zijn.

In de volgende sectie leer je hoe je toegang krijgt tot de opgeslagen hash voor het wachtwoord en hoe je het authenticeert met een opgegeven wachtwoord bij het inloggen/verifiëren.

Toegang krijgen tot het versleutelde wachtwoord en het gebruiken voor authenticatie

Je hebt tot nu toe geleerd over BcryptJS, hashen, salting, en het ontwikkelen van een express-server die nieuwe gebruikers aanmaakt met wachtwoorden die zijn opgeslagen als hashes. Nu leer je hoe je het opgeslagen wachtwoord kunt gebruiken en een gebruiker kunt authenticeren wanneer deze probeert in te loggen op de applicatie.

Om een ​​authenticatiemethode toe te voegen, voeg je de volgende route toe in je auth.js na de /signup-route:

  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 gebruiken we het /auth/login sub-pad om in te loggen en de authenticatie uit te voeren voor een al bestaande gebruiker. Net als bij het /auth/signup-eindpunt zal dit een async-await functie zijn omdat BcryptJS beloftes retourneert.

Ten eerste gebruiken we de findOne-functie van de Mongoose-bibliotheek, die wordt gebruikt om een document in een collectie te vinden op basis van een opgegeven zoekopdracht. In dit geval zoeken we naar de gebruiker op basis van het e-mailadres. Als er geen gebruiker met het opgegeven e-mailadres bestaat, wordt er een antwoord verzonden met statuscode 400 voor ongeldige referenties. (Het is geen goede praktijk om aan te geven welke parameter onjuist is tijdens het inloggen, omdat aanvallers die informatie kunnen gebruiken om bestaande accounts te vinden).

Als de gebruiker met het opgegeven e-mailadres bestaat, gaat het programma over naar wachtwoordvergelijking. Hiervoor biedt BcryptJS de compare() methode, die een ruwe string als eerste argument en een hash (met of zonder salt) als tweede argument neemt. Vervolgens retourneert het een Boolean promise; true als het wachtwoord overeenkomt met de hash en false als dat niet het geval is. Daarna kunt u een eenvoudige controle toevoegen met behulp van een if-statement en succes of fout retourneren op basis van de vergelijking.

Tenslotte zult u de express router exporteren met behulp van module.exports voor het startpunt app.js om het te gebruiken voor routes.

Om deze route te testen, kunt u een andere HTTP-respons naar deze URL http://localhost:3300/auth/login sturen met de volgende body:

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

Deze aanvraag zal de volgende reactie geven:

{
  "success": "Authenticated!"
}

Uiteindelijk zal auth.js er als volgt uitzien:

  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;

Conclusie

Deze tutorial heeft een server gemaakt om het gebruik van BcryptJS voor het veilig opslaan en benaderen van database-wachtwoorden uit te leggen. U kunt hier verder op ingaan door:

  • Meer routes implementeren voor andere taken zoals het ophalen van gebruikersreferenties, bijwerken van gebruikersreferenties, enz.

  • Het implementeren van verificatietokens om als antwoorden te verzenden, enzovoort.

Dit markeert het begin van je reis om wachtwoorden veilig te behandelen; je kunt altijd meer beveiliging toevoegen met verschillende technieken en technologieën, waardoor je meer veilige en veerkrachtige toepassingen kunt maken.

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