Accesso Area Riservata - Login con password_hash - Tecnologia Anno 5 Modulo 3 Lezione 5 | Prof. Giagnotti
Tecnologia · Anno 5 · Modulo 3 · Lezione 5 - Area Riservata Pro
✍ Autore: profgiagnotti · Materia: Tecnologia - Login Sicuro con Hash · password_hash + ruolo admin/user
Area riservata completa: connessione, login con password_verify, registrazione con controllo duplicati, ruolo admin/user, logout e landing protetta
Realizziamo un sistema reale: database area_riservata con ENUM ruolo, registrazione che fa hash con PASSWORD_DEFAULT (bcrypt), controllo username già esistente con store_result, login con prepared + password_verify + sessione + controllo ruolo admin, landing_page protetta, logout che distrugge tutto e CSS moderno. Test completo in htdocs/area_riservata.
Anno 5Modulo 3Lezione 5password_hashLogin realeRuoli~70 min
OBIETTIVI
✓Creare DB area_riservata e tabella utenti con id, username UNIQUE, password VARCHAR(255), ruolo ENUM admin/user
✓Implementare connessione.php OOP e capire perché VARCHAR(255) per hash bcrypt
✓Realizzare registrazione.php con password_hash(), controllo duplicati con prepare+store_result+num_rows
✓Realizzare login.php con session_start(), prepared SELECT, password_verify(), gestione ruoli e messaggi errore
✓Realizzare landing_page.php protetta, logout.php completo, stile.css moderno e test in htdocs/area_riservata
✓Evitare errori comuni: md5/sha1, password in chiaro, header dopo output, mysqli vs OOP mix
Ipotizziamo di realizzare una login page contenente un form per il login ed un pulsante di registrazione. L'utente deve autenticarsi per accedere all'area riservata (una landing page). Se il login non va a buon fine lo script visualizza errore (username o password errata oppure utente non registrato). Se l'utente non è registrato, cliccando su "registra" si registra e viene reindirizzato alla login page. Se registrato e login ok → landing page.
FLUSSO AREA RISERVATA
2. Realizzazione di connessione.php
Si vedano guide precedenti. Utilizziamo solito script OOP per connetterci al database area_riservata:
Se metti VARCHAR(50) e fai password_hash() con bcrypt, l'hash è 60 caratteri ma con Argon2id (PHP 8+) arriva a 97, con future estensioni anche 255. Se tronchi hash, password_verify() fallirà sempre. Quindi sempre 255. Altro errore che vedo: mettere password UNIQUE → no! Due utenti possono avere stessa password (hash diversi per salt), UNIQUE va solo su username/email.
3. Realizzazione di login.php
Realizziamo script con prima parte PHP e seconda HTML. Step:
Avviamo sessione per mantenere nome utente o ruolo.
Prepariamo query con parametro per evitare SQL Injection.
Eseguiamo query.
Se risultato: verifichiamo password con password_verify(), se ok salviamo sessione e redirect a landing, altrimenti errore "Password errata" / "Non sei amministratore".
Se non risultati: errore "Utente non trovato".
Chiudiamo statement e connessione.
Form login in HTML con link Registrati.
PHP - LOGIN.PHP
<?php
// 1. Avviamo sessione per mantenere nome utente o ruolo
session_start();
// 2. Includiamo connessione al database
require_once "connessione.php";
$error = ""; // Inizializza variabile errore
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 3. Acquisiamo dati login inseriti dall'utente
$username = trim($_POST["username"]);
$password = $_POST["password"];
// 4. Controllo connessione valida
if (!$conn) {
die("Errore di connessione: " . mysqli_connect_error());
}
// 5. Query per verificare se esiste utente con username fornito
$sql = "SELECT id, username, password, ruolo
FROM utenti
WHERE username = ?";
// 6. Prepariamo e associamo parametro per evitare SQL Injection
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $username);
// 7. Eseguiamo e otteniamo risultato
$stmt->execute();
$result = $stmt->get_result();
// 8. Se otteniamo risultato
if ($row = $result->fetch_assoc()) {
// Verifichiamo che password inserita corrisponda a hash salvato
if (password_verify($password, $row["password"])) {
// Autenticazione riuscita: salviamo in sessione
$_SESSION["username"] = $row["username"];
$_SESSION["ruolo"] = $row["ruolo"];
$_SESSION["user_id"] = $row["id"];
// Controllo ruolo: solo admin può entrare (logica del tuo testo originale)
if ($_SESSION["ruolo"] == "admin") {
header("Location: landing_page.php");
exit();
} else {
$error = "Non sei amministratore! Il tuo ruolo è: " . htmlspecialchars($row["ruolo"]);
}
} else {
// 9. Password errata
$error = "Password errata.";
}
} else {
// 10. Utente non trovato
$error = "Utente non trovato.";
}
// 11. Chiude statement e connessione
$stmt->close();
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Login - Area Riservata</title>
<link rel="stylesheet" href="stile.css">
</head>
<body>
<div class="container">
<h2>Login</h2>
<!-- Messaggio errore rosso -->
<?php
if (!empty($error)) {
echo "<p class='error'>" . htmlspecialchars($error) . "</p>";
}
?>
<form method="POST" action="">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Accedi</button>
</form>
<!-- Link registrazione -->
<a href="registrazione.php" class="btn">Registrati</a>
</div>
</body>
</html>
📖 password_hash() vs md5/sha1 - Fondamentale
password_hash($password, PASSWORD_DEFAULT) crea hash con bcrypt (60 char) con salt casuale automatico + costo adattivo. Ogni volta hash diverso anche per stessa password → sicuro. password_verify($plain, $hash) confronta plain con hash, restituisce true/false. Gestisce salt automaticamente. md5() e sha1() sono veloci e senza salt → attaccante con rainbow table cracca milioni di password al secondo. MAI usarli. All'esame se vedo md5, è errore grave. Usate sempre password_hash/verify, è lo standard PHP da 10 anni.
💡 Mix OOP e procedurale - Attenzione
Nel tuo codice originale c'è $conn = new mysqli() OOP + poi mysqli_prepare($conn,$sql) procedurale. Funziona ma è incoerente. Meglio tutto OOP: $stmt = $conn->prepare($sql) oppure tutto procedurale: $conn = mysqli_connect() + mysqli_prepare(). Ho lasciato versione OOP coerente sopra: $conn->prepare() e $stmt->bind_param(). Se usate procedurale, ricordate di passare $conn sempre come primo parametro.
4. Realizzazione di registrazione.php
Step: import connessione, verifica POST, hash password con PASSWORD_DEFAULT (bcrypt), ruolo default user, controllo se username già in uso con prepare + store_result + num_rows, se già in uso errore, altrimenti INSERT con bind_param, messaggio successo + link login, chiusura statement e connessione, form HTML.
PHP - REGISTRAZIONE.PHP
<?php
require 'connessione.php';
$msg = "";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST["username"]);
// Hash della password con algoritmo moderno bcrypt
$password = password_hash($_POST["password"], PASSWORD_DEFAULT);
// Ruolo predefinito utente normale
$ruolo = 'user';
// Controlliamo se username già in uso
$checkUser = $conn->prepare("SELECT id FROM utenti WHERE username = ?");
$checkUser->bind_param("s", $username);
$checkUser->execute();
$checkUser->store_result();
if ($checkUser->num_rows > 0) {
// Username già scelto da altro utente
$msg = "<p class='error'>Errore: Username già in uso.</p>";
} else {
// Inserimento nuovo utente
$stmt = $conn->prepare(
"INSERT INTO utenti (username, password, ruolo) VALUES (?, ?, ?)"
);
$stmt->bind_param("sss", $username, $password, $ruolo);
if ($stmt->execute()) {
$msg = "<p class='success'>Registrazione completata! <a href='login.php'>Vai al login</a></p>";
} else {
$msg = "<p class='error'>Errore: " . htmlspecialchars($stmt->error) . "</p>";
}
$stmt->close();
}
$checkUser->close();
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Registrazione</title>
<link rel="stylesheet" href="stile.css">
</head>
<body>
<div class="container">
<h2>Registrazione</h2>
<?php
if (!empty($msg)) {
echo $msg;
}
?>
<form method="POST" action="">
<label>Username:</label>
<input type="text" name="username" required>
<label>Password:</label>
<input type="password" name="password" required>
<button type="submit">Registrati</button>
</form>
<a href="login.php" class="btn">Torna al Login</a>
</div>
</body>
</html>
Questa è la base di ogni pagina riservata: session_start() come prima riga, poi if(!isset($_SESSION["username"])) redirect. Senza questo check, chiunque conosca URL landing_page.php entra senza login! In L6 aggiungeremo controllo ruolo: if($_SESSION["ruolo"] != "admin") die("Accesso negato"). Per ora admin solo può entrare (logica del tuo testo), ma in progetto reale user dovrebbe entrare in user_dashboard.php e admin in admin_dashboard.php. Ve lo faccio fare come esercizio.
A questo punto possiamo testare la nostra soluzione. Apriamo XAMPP, avviamo Apache e MySQL. Clicchiamo "admin" in MySQL → phpMyAdmin → tab SQL e inseriamo:
SQL - CREAZIONE DB AREA_RISERVATA - OTTIMIZZATO
CREATE DATABASE IF NOT EXISTS area_riservata;
USE area_riservata;
CREATE TABLE IF NOT EXISTS utenti (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
ruolo ENUM('admin', 'user') NOT NULL DEFAULT 'user'
);
-- Inseriamo un admin di test con password hashata (password: Admin123!)
-- Hash generato con: password_hash("Admin123!", PASSWORD_DEFAULT)
INSERT INTO utenti (username, password, ruolo)
VALUES (
'admin',
'$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'admin'
);
STRUTTURA CARTELLE HTDOCS
Dopo aver creato DB e tabella, creiamo cartella in htdocs chiamata area_riservata e salviamo al suo interno gli script PHP e CSS visti sopra.
Apriamo browser e scriviamo:
URL - TEST LOCALE
http://localhost/area_riservata/login.php
Testate: 1) Registrazione nuovo utente → controllo duplicato, 2) Login con password errata → messaggio rosso, 3) Login admin → landing_page con Benvenuto, 4) Accesso diretto a landing_page senza login → redirect a login.php, 5) Logout → sessione distrutta → landing non accessibile.
🚀 Esercizi per casa - Estensioni
Esercizio 1: Modifica login.php per permettere accesso anche a user (non solo admin): se ruolo=user → user_dashboard.php, se admin → admin_dashboard.php. Esercizio 2: Aggiungi campo email in registrazione con UNIQUE e controllo email già esistente. Esercizio 3: Aggiungi "Ricordami" con token sicuro (come visto in L4) invece di username in chiaro. Esercizio 4: Aggiungi password strength meter con JS: minimo 8 caratteri, maiuscola, numero, simbolo.
🐙 Laboratori Pratici
Vuoi passare all'azione? Accedi alla repository ufficiale GitHub del corso per scaricare il file del laboratorio pratico
Hai realizzato un sistema completo di area riservata professionale: DB con ENUM ruolo, connessione OOP, registrazione con password_hash() e controllo duplicati con store_result/num_rows, login con prepared + password_verify + sessione + controllo ruolo admin, landing_page protetta con check sessione, logout completo con distruzione cookie PHPSESSID, CSS moderno. Sai evitare md5/sha1, mix OOP/procedurale incoerente, header dopo output. Sai testare tutto in htdocs/area_riservata. Prossima lezione: CRUD utenti admin, gestione ruoli, protezione CSRF e validazione lato server avanzata.