Come gestire le password in modo sicuro con BcryptsJS in JavaScript

Introduzione

Proteggere le password dei siti web è una competenza essenziale che ogni sviluppatore dovrebbe avere. JavaScript offre un’opzione per garantire la memorizzazione sicura e l’elaborazione di password o altri dati sensibili utilizzando gli algoritmi di hash forniti dal modulo BcryptJS di JavaScript.

In questo tutorial, imparerai su BcryptJS e l’hashing per configurare un server express di base che archivierà le password come hash in un database invece di stringhe non elaborate e le recupererà per autenticare la password.

Prerequisiti

Per continuare con questo tutorial, è necessario avere la seguente configurazione.

  1. Una versione stabile di Node.js installata sul tuo computer con versione 12.x o superiore. Puoi utilizzare questo tutorial di DigitalOcean per installare l’ultima versione di Node Js sul tuo computer.

  2. Dovresti conoscere come programmare in JavaScript.

  3. _Dovresti avere Express JS installato sul tuo computer. Puoi utilizzare questa guida per imparare come configurare un server Express.

  4. Infine, avrai bisogno del database MongoDB community o Atlas per completare questo tutorial. Puoi installarlo utilizzando una di queste guide di DigitalOcean su_ come installare MongoDB.

Perché usare BcryptJS?

Bcrypt è un algoritmo di hash per creare hash per le password e memorizzarle in caso di violazione dei dati. Questo algoritmo di hash avanzato utilizza i salt, rendendolo difficile da violare tramite attacchi come il brute-forcing.

BcryptJS è l’implementazione JavaScript dell’algoritmo di hashing Bcrypt, che ti permette di utilizzare la crittografia di hash senza dover gestire complesse funzioni di hashing. Alcune delle ragioni che rendono BcryptJS una scelta eccellente per la sicurezza delle password sono le seguenti:

  1. Sicurezza – BcryptJS implementa l’algoritmo Bcrypt, un algoritmo lento (cosa positiva per l’hashing) che richiede una intensa potenza computazionale. Ciò rende estremamente difficile per gli attaccanti decifrare l’hash della password, garantendo la sicurezza delle password anche in caso di violazione dei dati.

  2. Salatura – BcryptJS gestisce la generazione di sale casuali per le password al fine di garantire la sicurezza dello storage (approfondiremo meglio gli hash e le sale nella prossima sezione). Le sale rendono l’hash di una password relativamente debole più complesso, rendendolo più difficile da decifrare.

  3. Facilità d’uso – BcryptJS fornisce agli sviluppatori JavaScript uno strumento per crittografare le loro password senza richiedere una profonda comprensione dell’hashing.

Nel prossimo passaggio, impareremo brevemente cosa sono gli hash e i salt.

Come funziona l’hashing?

Prima di utilizzare questi concetti nei tuoi progetti, devi capire come funziona l’hashing e il salting.

Hashing

L’hashing consiste nella conversione di una semplice stringa o testo in una stringa di caratteri casuali (crittografia). Questo permette di memorizzare e/o trasmettere in modo sicuro dati sensibili. L’hashing coinvolge i seguenti passaggi chiave:

Inserimento dei dati – Inizialmente, i dati che possono essere di qualsiasi tipo (binario, carattere, decimale, ecc.) come testo semplice o stringa vengono memorizzati.

La funzione di hash – La funzione di hash è un algoritmo matematico che prende in input i dati e li converte in un insieme di caratteri o codici di hash. Le funzioni di hash sono deterministiche (producono lo stesso output per lo stesso input) e funzioni unidirezionali (ciò significa che è quasi impossibile invertire l’output delle funzioni di hash, cioè l’hash nel suo dato di input).

Resistenza alla collisione – Questo significa che una funzione di hash viene creata tenendo presente l’idea di resistenza alla collisione, cioè due input diversi non possono avere lo stesso output (codice di hash).

Autenticazione – Le funzioni di hash sono deterministiche, producendo lo stesso hash per lo stesso input. Pertanto, durante l’autenticazione di una password archiviata come hash, l’idea generale è che se la password da autenticare corrisponde all’hash archiviato nel database, la password è corretta.

Salt

Dato che le funzioni di hash esistono da decenni, sono stati sviluppati strumenti come le tabelle rainbow, che contengono miliardi di voci di dati contenenti stringhe di dati e i loro hash corrispondenti basati su diversi algoritmi di hash.

Ora, considera una situazione in cui un utente crea un account sul tuo sito web utilizzando una password debole. Pertanto, in caso di violazione dei dati, un attaccante può cercare gli hash dei tuoi utenti e trovare la corrispondenza dell’hash per l’account utente con una password debole. Questo sarebbe disastroso in applicazioni ad alta sicurezza. Per evitare che ciò accada, vengono utilizzati i salti.

Il salting è uno strato aggiuntivo di sicurezza aggiunto agli hash aggiungendo una stringa casuale di caratteri all’hash di una password prima di archiviarla in un database. Pertanto, anche se i dati vengono violati, sarà difficile per un attaccante decifrare un hash contenente un salt. Considera il seguente esempio:

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

Come possiamo vedere chiaramente, la password memorizzata con sale è meno probabile da craccare a causa della natura deterministica delle funzioni di hash. Pertanto, se un attaccante, ad esempio, cerca questa stringa hash+password in una tabella arcobaleno, non otterrà la password effettiva ma qualcosa di completamente diverso.

Ora sei pronto per utilizzare BcryptJSe proteggere le tue password in modo standard nel settore.

Installazione di BcryptJS e altri moduli necessari

Ora che conosci l’hashing e il salt, ti basta prendere il tuo computer e iniziare a programmare. La struttura del progetto sarà la seguente:

Project Structure

Inizieremo creando un progetto npm con i seguenti passaggi:

  1. Apri una cartella e crea un file chiamato app.js.

  2. Apri una finestra del terminale in questa cartella e digita il comando

npm init

Dopo questo, ti verrà chiesto di inserire degli input ma puoi premere Invio senza fornire alcun input.

  1. Successivamente, crea altri 3 file, chiamati
  • auth.js
  • db.js
  • User.js
  1. Nella stessa finestra del terminale, digita il seguente comando per installare i pacchetti necessari.
npm install express mongoose BcryptJS nodemon

Ora hai un ambiente di progetto completo da seguire in questo tutorial. Nel passaggio successivo, imparerai come creare un server per utilizzare BcryptJS per memorizzare e autenticare in modo sicuro le password con MongoDB.

Creazione di un server con Express JS

Ora che hai impostato la struttura del progetto, puoi creare un server che utilizzi bcryptjs per proteggere le password memorizzandole come hash e autenticandole. Creeremo il server seguendo i seguenti passaggi corretti.

Passaggio 1 – Creazione di una connessione al database MongoDB

Per connetterti a mongoDB, stiamo utilizzando la versione community edition. Per mantenere organizzato il progetto, salverai il codice per impostare una connessione nel file 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;

Qui, importi il pacchetto mongoose, che fornisce un’API per connettere JavaScript a MongoDB. Inoltre, in questo caso l’URI di connessione è per un’installazione locale di MongoDB, se stai utilizzando un database cloud (come Atlas) devi solo modificare l’URI con quello specifico del tuo database.

Informazioni
Un URI è simile a un URL del server con la differenza che un URI può identificare il nome e l’identità delle risorse e la loro posizione su Internet. In contrasto, un URL è un sottoinsieme di un URI in grado di fare solo quest’ultimo.

La funzione connectToMongo è una funzione asincrona perché mongoose.connect restituisce una promessa JavaScript. Questa funzione fornirà una connessione in caso di esecuzione corretta. In caso contrario, restituirà un errore.

Infine, utilizziamo module.exports per esportare questa funzione ogni volta che il modulo db.js viene importato.

Passaggio 2 – Creazione di uno schema utente

Avrai bisogno di uno schema di base per creare o autenticare gli utenti con un database MongoDB. Se non sai cosa sia uno schema, puoi utilizzare questa eccellente guida di DO per capire e creare lo schema in MongoDB. Utilizzeremo solo due campi per il nostro schema, email e password.

Utilizza il seguente codice nel tuo modulo 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)

Creiamo due campi in questo schema, email e password. Entrambi sono campi obbligatori e di tipo stringa. Inoltre, email è un campo unico, il che significa che un’email può essere utilizzata solo una volta per avere un account su questo server.

Infine, esportiamo un modello chiamato users. Un modello rappresenta uno schema sul lato del database. Si può pensare a uno schema come una regola per definire un modello, mentre il modello viene memorizzato come una collezione nel database MongoDB.

È possibile convertire uno schema in un modello utilizzando la funzione model() della libreria mongoose.

Passo 3 – Configurazione del server in app.js

Dopo aver seguito i passaggi precedenti, hai creato con successo un modello e un modulo per stabilire una connessione al database MongoDB. Ora imparerai a configurare il server. Utilizza il seguente codice nel tuo file 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. });

Qui, importiamo i moduli express e db.js per connetterci a MongoDB. Successivamente, utilizziamo il middleware express.json() per gestire le risposte JSON. Le rotte vengono create in un modulo separato (auth.js) per mantenere il codice pulito e organizzato. Infine, creiamo un endpoint per far ascoltare il server sulla porta 3300 in localhost. (Puoi utilizzare qualsiasi porta a tua scelta)

Cifrare le password e memorizzarle in un database MongoDB

Fino a questo punto, il server è quasi pronto e ora creerai dei punti di accesso per il server. Creeremo due punti di accesso – signup e login. Nel punto di accesso di registrazione (signup), prenderemo l’email e la password di un nuovo utente e la memorizzeremo con crittografia utilizzando BcryptJS.

Nel file auth.js, inserisci il seguente codice:

  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;

Facciamo le importazioni necessarie e poi impostiamo il router di Express per creare il punto di accesso /signup. Utilizziamo il metodo POST in modo che le credenziali non siano divulgate nell’URL dell’applicazione. Dopo di ciò, creiamo un salt utilizzando la funzione genSalt del pacchetto scripts; il parametro passato alla funzione genSalt() contiene la lunghezza dei caratteri del salt. Poi utilizziamo la funzione hash() di BcryptJS, che richiede un parametro obbligatorio, la stringa della password da convertire in codice hash, e un argomento opzionale, la stringa del salt. E poi restituisce un hash che contiene sia la password che il salt.

Dopo ciò, utilizziamo la funzione create() del modulo mongoose per creare un documento nel nostro database definito dalle regole del modello users. Prende un oggetto Javascript contenente email e password, ma anziché fornirgli una stringa grezza, forniamo secPass (hash della password + sale) da memorizzare nel database. In questo modo, abbiamo memorizzato in modo sicuro una password nel database utilizzando il suo hash combinato con un sale anziché una stringa grezza. Infine, restituiamo una risposta JSON contenente il modello dell’utente. (Questo metodo di invio delle risposte è solo per la fase di sviluppo; in produzione, sostituirai questo con un token di autenticazione o altro).

Per testare questo endpoint, devi prima eseguire il server, che può essere fatto digitando il seguente comando nel terminale.

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

Questo comando eseguirà il tuo server su localhost e sulla porta 3300 (o qualsiasi altra porta specificata). Successivamente, puoi inviare una richiesta HTTP all’URL http://localhost:3300/auth/signup con il seguente body:

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

Questo produrrà l’output/risposta seguente:

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

Nota: L’hash della password e l’ID non saranno gli stessi, poiché sono sempre unici.

Nella sezione seguente, imparerai come accedere all’hash memorizzato per la password e autenticarlo con una password fornita durante il login/autenticazione.

Accedere alla password crittografata e usarla per l’autenticazione

Fino ad ora, hai imparato su BcryptJS, hashing, salting, e hai sviluppato un server express che crea nuovi utenti con password memorizzate come hash. Ora imparerai come utilizzare la password memorizzata e autenticare un utente quando prova a effettuare il login nell’applicazione.

Per aggiungere un metodo di autenticazione, aggiungi il seguente percorso nel file auth.js dopo il percorso /signup:

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

In questo caso, utilizziamo il sottopercorso /auth/login per eseguire l’autenticazione di accesso per un utente già esistente. Similmente al punto di accesso /auth/signup, questa sarà una funzione async-await poiché BcryptJS restituisce promesse.

Innanzitutto, utilizziamo la funzione findOne della libreria Mongoose, che viene utilizzata per trovare un documento in una collezione in base a una determinata query di ricerca. In questo caso, stiamo cercando l’utente in base all’email. Se non esiste nessun utente con l’email fornita, verrà inviata una risposta con il codice di stato 400 per credenziali non valide. (Non è una buona pratica fornire quale parametro è errato durante il login, poiché gli attaccanti possono utilizzare queste informazioni per trovare account esistenti).

Se l’utente con l’email fornita esiste, il programma passa al confronto delle password. A tal fine, BcryptJS fornisce il metodo compare(), che prende una stringa non criptata come primo argomento e un hash (con o senza salt) come secondo argomento. Restituisce quindi una promessa booleana; true se la password corrisponde all’hash e false se non corrispondono. Successivamente, puoi aggiungere un semplice controllo utilizzando un’istruzione if e restituire successo o errore in base al confronto.

Infine, esporterai il router di express utilizzando module.exports per utilizzarlo come punto di partenza app.js per le route.

Per testare questa route, puoi inviare un’altra risposta HTTP a questo URL http://localhost:3300/auth/login con il corpo come segue:

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

Questa richiesta darà la seguente risposta:

{
  "success": "Authenticated!"
}

Infine, auth.js avrà questo aspetto:

  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

Questo tutorial ha creato un server per spiegare l’utilizzo di BcryptJS per memorizzare e accedere in modo sicuro alle password del database. Puoi approfondire ulteriormente implementando:

  • Ulteriori route per altre operazioni come ottenere le credenziali dell’utente, aggiornare le credenziali dell’utente, ecc.

  • Implementare token di autenticazione da inviare come risposte, ecc.

Questo inizia il tuo percorso per gestire le password in modo sicuro; puoi sempre aggiungere ulteriori misure di sicurezza con diverse tecniche e tecnologie, consentendoti di creare applicazioni più sicure e robuste.

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