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 5 Modulo 3 Lezione 5 password_hash Login reale Ruoli ~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

1. Problema - Accesso all'area riservata

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
login.phpForm + Link Registrati Verifica DBpassword_verify()ruolo admin/user OK admin landing_page.php KO / no admin Errore rosso registrazione.phpHash + check duplicatoINSERT nuovo user Utente non registrato → Registrati → poi torna a login.php

2. Realizzazione di connessione.php

Si vedano guide precedenti. Utilizziamo solito script OOP per connetterci al database area_riservata:

PHP - CONNESSIONE.PHP - OOP - OTTIMIZZATO
<?php $servername = "localhost"; $username = "root"; $password = ""; $database = "area_riservata"; $conn = new mysqli($servername, $username, $password, $database); if ($conn->connect_error) { die("Connessione fallita: " . $conn->connect_error); } ?>
💬 Nota docente - Perché VARCHAR(255) per password

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:

  1. Avviamo sessione per mantenere nome utente o ruolo.
  2. Includiamo connessione al database.
  3. Verifichiamo metodo POST e acquisiamo dati login.
  4. Controlliamo connessione valida, altrimenti die().
  5. Query per controllare se username presente.
  6. Prepariamo query con parametro per evitare SQL Injection.
  7. Eseguiamo query.
  8. Se risultato: verifichiamo password con password_verify(), se ok salviamo sessione e redirect a landing, altrimenti errore "Password errata" / "Non sei amministratore".
  9. Se non risultati: errore "Utente non trovato".
  10. Chiudiamo statement e connessione.
  11. 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>
REGISTRAZIONE FLOW + STORE_RESULT
Input: username+ password plain password_hash()PASSWORD_DEFAULTbcrypt 60 char + salt Check duplicatoSELECT id WHERE username=?store_result + num_rows INSERT se liberobind_param sssuser, hash, ruolo

5. Realizzazione di logout.php

File molto semplice, già visto in Lezione 4:

PHP - LOGOUT.PHP - OTTIMIZZATO
<?php session_start(); session_unset(); session_destroy(); // Opzionale: elimina cookie sessione if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } header("Location: login.php"); exit(); ?>

6. Realizzazione di landing_page.php

Landing che visualizza benvenuto e link logout, ma solo se sessione attiva, altrimenti redirect a login:

PHP - LANDING_PAGE.PHP - PROTETTA - OTTIMIZZATA
<?php session_start(); if (!isset($_SESSION["username"])) { header("Location: login.php"); exit(); } ?> <!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <title>Landing Page - Area Riservata</title> <link rel="stylesheet" href="stile.css"> </head> <body> <div class="welcome-container"> <h1>Benvenuto, <?php echo htmlspecialchars($_SESSION["username"]); ?>!</h1> <p>Ruolo: <strong><?php echo htmlspecialchars($_SESSION["ruolo"]); ?></strong></p> <p>Hai accesso all'area riservata.</p> <a href="logout.php" class="btn">Logout</a> </div> </body> </html>
💬 Nota docente - Protezione landing_page

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.

7. Realizzazione di stile.css

CSS - STILE.CSS - OTTIMIZZATO E COMMENTATO
/* Impostazioni generali */ body { font-family: Arial, sans-serif; background-color: #f4f4f9; margin: 0; padding: 0; color: #333; } .container { max-width: 500px; margin: 50px auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); text-align: center; } .welcome-container { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; text-align: center; background-color: #f4f4f9; } .welcome-container h1 { font-size: 2.5em; color: #333; } h2 { color: #444; margin-bottom: 20px; } form { display: flex; flex-direction: column; gap: 15px; margin-top: 20px; } label { font-weight: bold; text-align: left; margin-bottom: 5px; } input[type="text"], input[type="password"] { padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } button { padding: 10px 15px; font-size: 16px; font-weight: bold; color: #fff; background-color: #007BFF; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #0056b3; } a.btn { display: inline-block; margin-top: 20px; padding: 10px 15px; font-size: 16px; text-decoration: none; color: #007BFF; border: 1px solid #007BFF; border-radius: 4px; background-color: transparent; } a.btn:hover { background-color: #007BFF; color: #fff; } p.error { color: #dc2626; font-weight: bold; background: #fef2f2; border: 1px solid #fecaca; padding: 10px; border-radius: 4px; } p.success { color: #166534; font-weight: bold; background: #dcfce7; border: 1px solid #bbf7d0; padding: 10px; border-radius: 4px; }

8. Test della soluzione al problema

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
htdocs/C:/xampp/htdocs/ area_riservata/cartella progetto connessione.php login.php registrazione.php logout.php landing_page.php stile.css localhost/area_riservata/login.php

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

🛠️ Script lezione 05 →

Vuoi consultare la sezione dedicata ai laboratori sul sito? Accedi cliccando il pulsante qui sotto.

📊 Sezione dedicata ai Laboratori

Vuoi consultare la sezione dedicata alle guide sul sito? Accedi cliccando il pulsante qui sotto.

📚 Guida a XAMPP →

🎓 Fine Lezione 5 - Cosa sai fare ora

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.