Come creare un indicatore di robustezza della password in JavaScript puro
In questo tutorial costruiremo, riga per riga, uno script JavaScript che valuta in tempo reale la robustezza di una password digitata dall'utente. Lo script calcola un punteggio numerico, lo traduce in un livello visivo (Weak, Medium, Good, Strong) e aggiorna una barra di avanzamento colorata direttamente nel DOM. Non servono librerie esterne: useremo soltanto le API native del browser.
Prerequisiti
Per seguire il tutorial ti servono conoscenze di base di HTML, CSS e JavaScript. Dovresti avere familiarità con i selettori del DOM, gli event listener e le espressioni regolari. Il codice è scritto in ES6, quindi useremo let, const, arrow function, template literal e parametri con valore predefinito.
Struttura HTML di riferimento
Prima di analizzare lo script, vediamo il markup minimo che lo script si aspetta di trovare nella pagina. Serve un campo di input con id="password" e, subito dopo di esso (come fratello diretto nel DOM), un contenitore con classe password-strength che contenga due elementi figli: una barra con classe password-strength-level e un'etichetta con classe password-strength-label.
<!-- Campo di input della password -->
<input type="password" id="password" />
<!-- Contenitore dell'indicatore di robustezza -->
<span class="password-strength hidden">
<span class="password-strength-level"></span>
<span class="password-strength-label"></span>
</span>
La classe hidden nasconde il contenitore finché l'utente non inizia a digitare. Lo script la rimuoverà e la aggiungerà dinamicamente.
Il guscio esterno: strict mode e IIFE
Il file inizia con due costrutti importanti che lavorano insieme per isolare il codice dal resto della pagina.
'use strict';
(function () {
// ...tutto il codice vive qui dentro...
})();
La direttiva 'use strict' attiva la modalità rigorosa di JavaScript: impedisce l'uso di variabili non dichiarate, vieta la cancellazione di proprietà non configurabili e rende più prevedibile il comportamento di this. È una rete di sicurezza che cattura errori comuni già in fase di sviluppo.
L'intero blocco è racchiuso in una IIFE (Immediately Invoked Function Expression). Le parentesi tonde attorno alla funzione anonima la trasformano in un'espressione, e le parentesi finali () la eseguono subito. Il vantaggio è che tutte le variabili e le funzioni dichiarate all'interno restano private: non inquinano lo scope globale e non possono entrare in conflitto con altri script presenti nella stessa pagina.
Calcolare il punteggio: la funzione passwordScore
Il cuore della logica è questa funzione. Riceve una stringa e restituisce un numero intero compreso tra 0 e 100.
// Calcola il punteggio della password su una scala da 0 a 100
function passwordScore(value = '') {
// Se la stringa è vuota o falsy, il punteggio è zero
if (!value) return 0;
let score = 0;
// Un punto per le lettere minuscole
if (/[a-z]/.test(value)) score += 1;
// Un punto per le lettere maiuscole
if (/[A-Z]/.test(value)) score += 1;
// Un punto per le cifre
if (/[0-9]/.test(value)) score += 1;
// Quattro punti per i caratteri speciali
if (/\W/.test(value)) score += 4;
// Un punto se la lunghezza è almeno 8
if (value.length >= 8) score += 1;
// Un punto aggiuntivo se la lunghezza è almeno 12
if (value.length >= 12) score += 1;
// Un punto aggiuntivo se la lunghezza è almeno 16
if (value.length >= 16) score += 1;
// Converte in percentuale e limita il massimo a 100
return Math.min(Math.round((score / 10) * 100), 100);
}
Analizziamo ogni passaggio nel dettaglio.
Il parametro value ha un valore predefinito pari a stringa vuota. Se la funzione viene chiamata senza argomenti, value sarà '' e il controllo if (!value) restituirà immediatamente 0. Questo evita errori nel caso in cui il campo sia vuoto.
La variabile score parte da zero e cresce man mano che la password soddisfa i vari criteri. Ogni criterio viene verificato con un'espressione regolare passata al metodo .test(), che restituisce true se trova almeno una corrispondenza nella stringa.
I criteri di varietà dei caratteri sono quattro. Le lettere minuscole (/[a-z]/), le maiuscole (/[A-Z]/) e le cifre (/[0-9]/) valgono ciascuna un punto. I caratteri speciali (/\W/) valgono quattro punti. La regex \W corrisponde a qualsiasi carattere che non sia lettera, cifra o underscore: include simboli come @, #, !, spazi e così via. Il peso maggiore dato ai caratteri speciali riflette il fatto che aumentano in modo significativo lo spazio di ricerca di un attacco brute-force.
I criteri di lunghezza sono tre e sono cumulativi. Una password di 16 caratteri soddisfa tutti e tre i controlli (>= 8, >= 12 e >= 16), guadagnando tre punti complessivi. Questo premia le passphrase lunghe.
Il punteggio massimo raggiungibile è 10 (1 + 1 + 1 + 4 + 1 + 1 + 1). La formula finale Math.round((score / 10) * 100) lo converte in percentuale. Math.min(..., 100) è una garanzia aggiuntiva: se in futuro si aggiungessero altri criteri, il risultato non supererebbe mai 100.
Tradurre il punteggio in un livello: la funzione strengthLevel
Questa funzione trasforma il numero restituito da passwordScore in un oggetto contenente un'etichetta leggibile e le classi CSS da applicare agli elementi del DOM.
// Restituisce etichetta e classi CSS in base al punteggio
function strengthLevel(score = 0) {
// Da 0 a 25: debole
if (score <= 25)
return {
label: 'Weak',
colorClass: 'password-strength-label text-danger',
barClass: 'password-strength-level bg-danger',
};
// Da 26 a 50: media
if (score <= 50)
return {
label: 'Medium',
colorClass: 'password-strength-label text-warning',
barClass: 'password-strength-level bg-warning',
};
// Da 51 a 75: buona
if (score <= 75)
return {
label: 'Good',
colorClass: 'password-strength-label text-medium',
barClass: 'password-strength-level bg-medium',
};
// Da 76 a 100: forte
return {
label: 'Strong',
colorClass: 'password-strength-label text-success',
barClass: 'password-strength-level bg-success',
};
}
La funzione usa una catena di if con return anticipato. Non servono else: ogni return interrompe l'esecuzione. Se il punteggio è 25 o meno, restituisce il livello Weak con classi rosse (text-danger, bg-danger). Se è tra 26 e 50, restituisce Medium con classi arancioni. Se è tra 51 e 75, restituisce Good con classi intermedie. Tutto ciò che supera 75 è Strong, con classi verdi.
L'oggetto restituito ha tre proprietà. label è il testo da mostrare all'utente. colorClass è la stringa completa di classi da assegnare all'elemento dell'etichetta testuale. barClass è la stringa di classi per la barra di avanzamento. Nota che entrambe le stringhe includono sempre la classe base dell'elemento (password-strength-label o password-strength-level): questo perché più avanti verrà usata la proprietà className, che sostituisce tutte le classi dell'elemento, non ne aggiunge.
Mostrare e nascondere l'indicatore: la funzione canShow
Questa piccola funzione gestisce la visibilità del contenitore dell'indicatore.
// Mostra o nasconde l'elemento in base alla lunghezza della password
function canShow(element = null, password = '') {
if (password.length > 0) {
// Se c'è almeno un carattere, rimuove la classe 'hidden'
element.classList.remove('hidden');
} else {
// Se il campo è vuoto, aggiunge la classe 'hidden'
element.classList.add('hidden');
}
}
La logica è diretta: se la password contiene almeno un carattere, l'elemento diventa visibile rimuovendo la classe hidden. Se la password è vuota (ad esempio dopo aver cancellato tutto il testo), l'elemento viene nascosto di nuovo aggiungendo hidden. L'uso di classList.add e classList.remove è sicuro: aggiungere una classe già presente o rimuoverne una assente non causa errori.
Rispondere all'input: la funzione handleEvent
Questa è la funzione che viene chiamata ogni volta che l'utente digita o incolla qualcosa nel campo password. Collega tutte le funzioni precedenti.
// Gestore dell'evento: aggiorna l'indicatore a ogni modifica del campo
function handleEvent(evt) {
// L'elemento che ha generato l'evento (l'input)
const inputElement = evt.target;
// L'elemento fratello successivo nel DOM (il contenitore dell'indicatore)
const checker = inputElement.nextElementSibling;
// Se non esiste o non ha la classe giusta, esce subito
if (!checker || !checker.classList.contains('password-strength')) {
return;
}
// Legge il valore attuale del campo
const password = inputElement.value;
// Trova la barra e l'etichetta dentro il contenitore
const level = checker.querySelector('.password-strength-level');
const label = checker.querySelector('.password-strength-label');
// Calcola punteggio, larghezza e dati del livello
const score = passwordScore(password);
const width = `${score}%`;
const data = strengthLevel(score);
// Mostra o nasconde il contenitore
canShow(checker, password);
// Aggiorna la larghezza della barra
level.style.width = width;
// Sostituisce tutte le classi della barra
level.className = data.barClass;
// Sostituisce tutte le classi dell'etichetta
label.className = data.colorClass;
// Aggiorna il testo dell'etichetta
label.innerText = data.label;
}
Vediamo il flusso completo. L'oggetto evento evt viene passato automaticamente dal browser. evt.target è il riferimento all'elemento che ha scatenato l'evento, cioè il campo di input.
La proprietà nextElementSibling restituisce l'elemento fratello immediatamente successivo nel DOM, ignorando i nodi di testo. Questo è il motivo per cui nel markup il contenitore password-strength deve trovarsi subito dopo l'input. Se il fratello non esiste o non ha la classe attesa, la funzione esce con un return silenzioso: è una protezione contro markup inatteso.
Il metodo querySelector viene usato per trovare la barra e l'etichetta all'interno del contenitore. La stringa con il punteggio percentuale (width) viene costruita con un template literal e applicata come stile inline tramite level.style.width. Le classi vengono sostituite per intero con className (non con classList), e il testo dell'etichetta viene aggiornato con innerText.
Collegare gli eventi: la funzione handleEvents
Questa funzione si occupa di cercare il campo password nel DOM e di registrare i listener necessari.
// Registra i listener sul campo password
function handleEvents() {
// Cerca l'elemento con id 'password'
const input = document.getElementById('password');
// Se non esiste, esce senza fare nulla
if (!input) {
return;
}
// Ascolta l'evento 'input' (digitazione)
input.addEventListener('input', handleEvent, false);
// Ascolta l'evento 'paste' (incolla)
input.addEventListener('paste', handleEvent, false);
}
La funzione cerca l'input con getElementById. Se non lo trova, esce: questo rende lo script sicuro da includere anche in pagine che non hanno un campo password.
Vengono registrati due listener. L'evento input si attiva a ogni battitura di tasto, compresa la cancellazione. L'evento paste intercetta l'incolla da clipboard, un caso che input potrebbe non coprire in tutti i browser. Il terzo parametro false indica che il listener opera in fase di bubbling (il comportamento predefinito), non in fase di capturing.
Avviare tutto al caricamento: DOMContentLoaded
L'ultima riga della IIFE collega handleEvents all'evento DOMContentLoaded del documento.
// Avvia tutto quando il DOM è pronto
document.addEventListener('DOMContentLoaded', handleEvents, false);
L'evento DOMContentLoaded viene emesso quando il parser HTML ha finito di costruire l'albero del DOM, senza attendere il caricamento di immagini, fogli di stile o iframe. È il momento più precoce in cui possiamo accedere agli elementi della pagina in modo sicuro. Se usassimo l'evento load al suo posto, lo script si attiverebbe solo dopo il download completo di tutte le risorse, introducendo un ritardo inutile.
Il codice completo
Ecco lo script nella sua interezza, con i commenti che riassumono ogni sezione.
'use strict';
(function () {
// Calcola il punteggio della password (0-100)
function passwordScore(value = '') {
if (!value) return 0;
let score = 0;
if (/[a-z]/.test(value)) score += 1; // minuscole
if (/[A-Z]/.test(value)) score += 1; // maiuscole
if (/[0-9]/.test(value)) score += 1; // cifre
if (/\W/.test(value)) score += 4; // caratteri speciali
if (value.length >= 8) score += 1; // lunghezza >= 8
if (value.length >= 12) score += 1; // lunghezza >= 12
if (value.length >= 16) score += 1; // lunghezza >= 16
return Math.min(Math.round((score / 10) * 100), 100);
}
// Restituisce etichetta e classi CSS per il livello corrente
function strengthLevel(score = 0) {
if (score <= 25)
return {
label: 'Weak',
colorClass: 'password-strength-label text-danger',
barClass: 'password-strength-level bg-danger',
};
if (score <= 50)
return {
label: 'Medium',
colorClass: 'password-strength-label text-warning',
barClass: 'password-strength-level bg-warning',
};
if (score <= 75)
return {
label: 'Good',
colorClass: 'password-strength-label text-medium',
barClass: 'password-strength-level bg-medium',
};
return {
label: 'Strong',
colorClass: 'password-strength-label text-success',
barClass: 'password-strength-level bg-success',
};
}
// Mostra o nasconde l'indicatore
function canShow(element = null, password = '') {
if (password.length > 0) {
element.classList.remove('hidden');
} else {
element.classList.add('hidden');
}
}
// Gestore dell'evento input/paste
function handleEvent(evt) {
const inputElement = evt.target;
const checker = inputElement.nextElementSibling;
if (!checker || !checker.classList.contains('password-strength')) {
return;
}
const password = inputElement.value;
const level = checker.querySelector('.password-strength-level');
const label = checker.querySelector('.password-strength-label');
const score = passwordScore(password);
const width = `${score}%`;
const data = strengthLevel(score);
canShow(checker, password);
level.style.width = width;
level.className = data.barClass;
label.className = data.colorClass;
label.innerText = data.label;
}
// Registra i listener sul campo password
function handleEvents() {
const input = document.getElementById('password');
if (!input) {
return;
}
input.addEventListener('input', handleEvent, false);
input.addEventListener('paste', handleEvent, false);
}
// Avvia tutto quando il DOM è pronto
document.addEventListener('DOMContentLoaded', handleEvents, false);
})();
Demo
JavaScript Password Strength Checker
Come funziona il flusso completo
Ricapitoliamo l'intero ciclo di vita dello script in ordine cronologico. Quando il browser incontra il file JavaScript, la IIFE si esegue immediatamente e registra un listener su DOMContentLoaded. Quando il DOM è pronto, viene chiamata handleEvents, che cerca il campo #password e vi aggancia i listener input e paste. Ogni volta che l'utente digita o incolla del testo, viene invocata handleEvent. Questa funzione legge il valore corrente del campo, calcola il punteggio con passwordScore, ottiene etichetta e classi con strengthLevel, decide se mostrare o nascondere l'indicatore con canShow, e infine aggiorna la larghezza della barra, le classi CSS e il testo dell'etichetta.
Possibili miglioramenti
Lo script è volutamente semplice e leggero, ma ci sono diverse direzioni in cui potresti estenderlo. Potresti aggiungere un controllo sulle sequenze ripetute (come aaa o 111) che riducono il punteggio. Potresti verificare che la password non contenga parole comuni da dizionario, usando una lista di termini frequenti. Un altro miglioramento utile sarebbe rendere lo script riutilizzabile su più campi nella stessa pagina, sostituendo getElementById con querySelectorAll e iterando su tutti gli input trovati. Infine, potresti aggiungere una transizione CSS sulla proprietà width della barra per ottenere un'animazione fluida durante la digitazione.