Introduzione
La password è uno dei dati più delicati raccolti da un form. Una validazione superficiale, come controllare soltanto che il campo non sia vuoto, può portare alla creazione di credenziali deboli e facilmente attaccabili.
In questo tutorial realizzeremo un form PHP per la registrazione di un utente. Il form verificherà:
- che la password sia presente;
- che abbia una lunghezza minima;
- che contenga caratteri di categorie diverse;
- che non includa spazi iniziali o finali;
- che la conferma corrisponda alla password originale;
- che la password non venga mai salvata in chiaro.
È importante distinguere validazione e sicurezza. La validazione verifica che il valore rispetti determinate regole, mentre la sicurezza richiede anche di proteggere la password durante il salvataggio, utilizzando funzioni come password_hash().
Codice completo
<?php
// Array che conterrà tutti gli errori mostrati all´utente.
$errors = [];
// Valori iniziali del form.
// La password non viene ripopolata dopo un errore per motivi di sicurezza.
$email = ´´;
if ($_SERVER[´REQUEST_METHOD´] === ´POST´) {
// Recuperiamo l´e-mail, eliminando solo gli spazi esterni.
$email = trim($_POST[´email´] ?? ´´);
// Le password non devono essere alterate con trim().
// Uno spazio potrebbe essere un carattere intenzionalmente inserito.
$password = $_POST[´password´] ?? ´´;
$passwordConfirmation = $_POST[´password_confirmation´] ?? ´´;
// Validazione dell´e-mail.
if ($email === ´´) {
$errors[´email´] = ´L’e-mail è obbligatoria.´;
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[´email´] = ´Inserisci un indirizzo e-mail valido.´;
}
// La password deve avere almeno 12 caratteri.
if ($password === ´´) {
$errors[´password´] = ´La password è obbligatoria.´;
} elseif (strlen($password) < 12) {
$errors[´password´] =
´La password deve contenere almeno 12 caratteri.´;
}
// Verifichiamo la presenza di almeno una minuscola, una maiuscola,
// una cifra e un carattere non alfanumerico.
if ($password !== ´´) {
if (!preg_match(´/[a-z]/´, $password)) {
$errors[´password´] =
´La password deve contenere almeno una lettera minuscola.´;
} elseif (!preg_match(´/[A-Z]/´, $password)) {
$errors[´password´] =
´La password deve contenere almeno una lettera maiuscola.´;
} elseif (!preg_match(´/[0-9]/´, $password)) {
$errors[´password´] =
´La password deve contenere almeno una cifra.´;
} elseif (!preg_match(´/[^a-zA-Z0-9]/´, $password)) {
$errors[´password´] =
´La password deve contenere almeno un simbolo.´;
}
}
// La conferma deve corrispondere esattamente alla password.
if ($passwordConfirmation === ´´) {
$errors[´password_confirmation´] =
´Conferma la password.´;
} elseif (!hash_equals($password, $passwordConfirmation)) {
$errors[´password_confirmation´] =
´Le due password non coincidono.´;
}
if (empty($errors)) {
// La password non viene mai salvata direttamente.
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
/*
* In un progetto reale, qui si inserirebbero nel database:
*
* $statement = $pdo->prepare(
* ´INSERT INTO users (email, password_hash)
* VALUES (:email, :password_hash)´
* );
*
* $statement->execute([
* ´email´ => $email,
* ´password_hash´ => $passwordHash
* ]);
*/
echo ´<p>Registrazione completata correttamente.</p>´;
// Dopo l´elaborazione è opportuno azzerare le variabili sensibili.
$password = ´´;
$passwordConfirmation = ´´;
}
}
// Funzione per stampare testo in sicurezza dentro l´HTML.
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, ´UTF-8´);
}
?>
<form method="post" action="">
<div>
<label for="email">E-mail</label>
<input
type="email"
id="email"
name="email"
value="<?= e($email) ?>"
required
>
<?php if (isset($errors[´email´])): ?>
<p><?= e($errors[´email´]) ?></p>
<?php endif; ?>
</div>
<div>
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
autocomplete="new-password"
>
<?php if (isset($errors[´password´])): ?>
<p><?= e($errors[´password´]) ?></p>
<?php endif; ?>
</div>
<div>
<label for="password_confirmation">Conferma password</label>
<input
type="password"
id="password_confirmation"
name="password_confirmation"
required
autocomplete="new-password"
>
<?php if (isset($errors[´password_confirmation´])): ?>
<p><?= e($errors[´password_confirmation´]) ?></p>
<?php endif; ?>
</div>
<button type="submit">Registrati</button>
</form> Spiegazione
Il codice utilizza il metodo POST, adatto per inviare dati sensibili perché evita di inserire i valori direttamente nell’URL. Tuttavia, POST da solo non cifra la comunicazione: in produzione il sito deve usare sempre HTTPS.
Il controllo $_SERVER[´REQUEST_METHOD´] === ´POST´ permette di eseguire la validazione solo quando l’utente invia il form. L’operatore ?? evita errori nel caso in cui un campo non sia stato inviato.
Per l’e-mail viene usato filter_var() con FILTER_VALIDATE_EMAIL. Per la password utilizziamo invece strlen() e alcune espressioni regolari. La funzione preg_match() cerca almeno un carattere appartenente alla categoria richiesta.
La password non viene sottoposta a trim(). Eliminare automaticamente gli spazi può modificare il valore inserito dall’utente. Inoltre, non è necessario mostrare nuovamente una password dopo un errore: il browser può ripulire i campi password e l’utente può reinserirli.
Il confronto tra password e conferma viene effettuato con hash_equals(). In questo caso è sufficiente anche un confronto diretto, ma hash_equals() rappresenta una scelta più attenta nei confronti di possibili attacchi basati sui tempi di esecuzione.
La funzione password_hash() genera un hash sicuro usando l’algoritmo predefinito più appropriato disponibile nella versione di PHP. Nel database deve essere salvato soltanto l’hash, mai la password originale. Durante il login si userà invece password_verify().
Best practice
- Usa HTTPS: protegge le credenziali durante il trasferimento tra browser e server.
- Non salvare password in chiaro: usa sempre password_hash() e password_verify().
- Non imporre regole inutilmente rigide: una password lunga è spesso più sicura di una breve con molti simboli obbligatori.
- Non usare regex per controllare ogni possibile password debole: evita soprattutto password comuni tramite una lista dedicata o un servizio affidabile.
- Non stampare direttamente input utente: usa htmlspecialchars(), come nella funzione e().
- Valida sempre lato server: gli attributi HTML come required e minlength possono essere disattivati o aggirati.
- Usa query preparate: quando salvi l’utente nel database, utilizza PDO con parametri nominati.
- Limita i tentativi di login: la validazione della registrazione non protegge da attacchi ripetuti sulla pagina di accesso.
Riepilogo
Un form per password deve controllare sia la qualità minima del valore sia la corrispondenza con il campo di conferma. La password non deve essere ripopolata dopo un errore e non deve mai essere salvata direttamente. Il flusso corretto è: ricezione dei dati, validazione server-side, visualizzazione sicura degli errori, generazione dell’hash e inserimento nel database tramite query preparata.
Questa struttura può essere estesa con regole personalizzate, messaggi localizzati, controllo delle password compromesse e registrazione degli eventi di sicurezza.
