TP MFA avec TOTP đâïž
đŻ Objectifs pĂ©dagogiques
- Comprendre le fonctionnement dâun second facteur TOTP (Timeâbased OneâTime Password).
- ImplĂ©menter un parcours dâauthentification Web : login/mot de passe â saisie du code TOTP.
- Générer un secret TOTP, un QR Code et vérifier les codes cÎté serveur.
- Appliquer des bonnes pratiques (hash de mot de passe, sessions, contrĂŽle dâaccĂšs).
1. Travail GuidĂ©âïž
1.1 PrĂ©paration du projetâïž
- Créez un dossier
mfa-php/avec la structure :
mfa-php/
ââ config.php
ââ schema.sql
ââ register.php
ââ login.php
ââ mfa_setup.php
ââ mfa_verify.php
ââ dashboard.php
ââ logout.php
ââ helpers/
ââ totp.php
2. Installez la librairie OTPHP avec Composer
đ Placez vous dans mfa-php/ et exĂ©cutez la commande suivante :
composer require spomky-labs/otphp
- Dans MySQL, créez une base
demoet créer la table ci-dessous.
1.2 ModĂšle de donnĂ©esâïž
Créez la table users :
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
mfa_secret VARCHAR(64) DEFAULT NULL,
mfa_enabled TINYINT(1) NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.3 SĂ©curitĂ© minimale attendueâïž
- Hash des mots de passe avec
password_hash()+ vérif viapassword_verify(). - Sessions sécurisées (
session_start()en dĂ©but de chaque page, vĂ©rifs dâaccĂšs). - Ne jamais stocker le mot de passe en clair, ni afficher le secret TOTP Ă lâĂ©cran (sauf pour debug en local si besoin).
1.4 Parcours utilisateur Ă implĂ©menterâïž
1.Inscription (register.php) : email + mot de passe â crĂ©e lâutilisateur.
2.Connexion (étape 1) (login.php) : vérifie email + mot de passe.
> Si mfa_enabled = 0 â redirection vers mfa_setup.php.
> Sinon â redirection vers mfa_verify.php.
3.Activation MFA (mfa_setup.php) :
> GénÚre un secret TOTP.
> Affiche un QR Code Ă scanner dans lâapplication mobile (URI otpauth://).
> VĂ©rifie un premier code entrĂ© par lâutilisateur.
> Si valide â mfa_enabled=1.
4.Vérification MFA (mfa_verify.php) : demande le code TOTP à chaque connexion.
5.Une fois validé, accÚs à dashboard.php (zone protégée). logout.php termine la session.
1.5 Liste des fichiersâïž
- register.php â formulaire email + mot de passe (POST) â redirection login.
- login.php : formulaire email + mot de passe (POST). Si OK et
mfa_enabled=1â redirectionmfa_verify.php, sinonmfa_setup.php. - mfa_setup.php : affiche QR + champ "Code TOTP" â si valide, enregistre
mfa_enabled=1et redirigedashboard.php. - mfa_verify.php : champ "Code TOTP" â si valide, session authentifiĂ©e complĂšte â
dashboard.php. - dashboard.php â page protĂ©gĂ©e avec âBienvenue
â.
â
Fonctionnel : inscription, login, setup MFA, vérification MFA, déconnexion.
â
Code : sĂ©paration logique, vĂ©rifs dâerreurs, messages clairs.
2. Tests & Validationâïž
- Créer un compte, puis se connecter.
- Ă la premiĂšre connexion, la page MFA Setup affiche le QR Code : scannezâle avec votre appli.
- Saisissez un code TOTP : lâactivation doit rĂ©ussir et vous envoyer au dashboard.
- DĂ©connectezâvous, reconnectezâvous : aprĂšs le mot de passe, la page MFA Verify est demandĂ©e ; entrez le code.
- VĂ©rifiez que lâaccĂšs direct Ă
dashboard.phpsans TOTP nâest pas possible (contrĂŽles de session).
Bonnes pratiques & Extensions
-
Sécurité :
- Utiliser
password_hash()/password_verify()(déjà fait). - Protéger les pages par des contrÎles de session stricts.
- Limiter les tentatives de connexion (ex. compteur + délai).
- Masquer le secret cÎté interface (affiché ici pour pédagogie seulement).
- Utiliser
-
Codes de secours (bonus) : générer 5à codes aléatoires hashés en BDD pour récupération.
- Souvenir dâappareil (bonus) : cookie signĂ© (HMAC) valide X jours, pour Ă©viter le TOTP Ă chaque login.
- DĂ©calage dâhorloge : afficher lâheure serveur et recommander la synchronisation de lâhorloge client.
đ§Ș DĂ©pannage (FAQ rapide)
- Le QR ne sâaffiche pas : vĂ©rifiez lâURL (encodage de lâURI
otpauth://). - Redirections bizarres : sessions non démarrées ?
session_start()doit ĂȘtre tout en haut de chaque script (viaconfig.php).
3. Correctionâïž
Corrigé & Code de référence
```php <?php // ===== config.php ===== ini_set('display_errors', 1); error_reporting(E_ALL); session_start();
$DB_HOST = '127.0.0.1'; $DB_NAME = 'mfa_demo'; $DB_USER = 'root'; $DB_PASS = '';
try { \(pdo = new PDO("mysql:host=\)DB_HOST;dbname=$DB_NAME;charset=utf8mb4", $DB_USER, $DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); } catch (PDOException $e) { die("Erreur de connexion DB : " . $e->getMessage()); }
function require_login() { if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; } }
function is_fully_authenticated() { return isset(\(_SESSION['user_id']) && (!(\)_SESSION['mfa_required'] ?? false)); }
// ===== mfa_setup.php ===== require DIR . '/config.php'; require DIR . '/vendor/autoload.php';
use OTPHP\TOTP;
require_login();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=?"); \(stmt->execute([\)_SESSION['user_id']]); $user = $stmt->fetch();
if (!$user) { header("Location: login.php"); exit; }
if ($user['mfa_enabled']) { header("Location: dashboard.php"); exit; }
if (empty($user['mfa_secret'])) { $totp = TOTP::create(); $secret = $totp->getSecret(); \(pdo->prepare("UPDATE users SET mfa_secret=? WHERE id=?")->execute([\)secret, $user['id']]); $user['mfa_secret'] = $secret; }
\(totp = TOTP::create(\)user['mfa_secret']); \(totp->setLabel(\)user['email']); $totp->setIssuer("DemoMFA"); $uri = $totp->getProvisioningUri(); \(qr = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=".urlencode(\)uri);
if ($_SERVER['REQUEST_METHOD'] === 'POST') { \(code = trim(\)_POST['code'] ?? ''); if (\(totp->verify(\)code)) { \(pdo->prepare("UPDATE users SET mfa_enabled=1 WHERE id=?")->execute([\)user['id']]); $_SESSION['mfa_required'] = false; header("Location: dashboard.php"); exit; } else { $error = "Code invalide."; } } ?> <!DOCTYPE html>
Activer MFA
Scannez ce QR Code avec Google Authenticator :
<img src="= \(qr ?>" alt="QR Code"><br> <form method="post"> <label>Entrez le code: <input type="text" name="code" required></label> <button type="submit">Activer</button> </form> <?php if (!empty(\)error)) echo "$error
"; ?>// ===== mfa_verify.php ===== require DIR . '/config.php'; require DIR . '/vendor/autoload.php';
use OTPHP\TOTP;
require_login();
if (!($_SESSION['mfa_required'] ?? false)) { header("Location: dashboard.php"); exit; }
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=?"); \(stmt->execute([\)_SESSION['user_id']]); $user = $stmt->fetch();
if (!\(user || !\)user['mfa_enabled']) { header("Location: login.php"); exit; }
\(totp = TOTP::create(\)user['mfa_secret']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') { \(code = trim(\)_POST['code'] ?? ''); if (\(totp->verify(\)code)) { $_SESSION['mfa_required'] = false; header("Location: dashboard.php"); exit; } else { \(error = "Code invalide."; } } ?> <!DOCTYPE html> <html lang="fr"> <head><meta charset="UTF-8"><title>Vérification MFA</title></head> <body> <h1>Vérification du code MFA</h1> <form method="post"> <input type="text" name="code" required> <button type="submit">Vérifier</button> </form> <?php if (!empty(\)error)) echo "
$error
"; ?>