Aller au contenu

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⚓

  1. Créez un dossier mfa-php/ avec la structure :

Verilog
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

  1. Dans MySQL, créez une base demo et créer la table ci-dessous.

1.2 ModĂšle de donnĂ©es⚓

Créez la table users :

🐬 SQL
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 via password_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.

Scdoc
- Si mfa_enabled = 0 → redirection vers mfa_setup.php.
- Sinon → redirection vers mfa_verify.php.

3.Activation MFA (mfa_setup.php) :

Scdoc
- 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 ⇒ redirection mfa_verify.php, sinon mfa_setup.php.
  • mfa_setup.php : affiche QR + champ "Code TOTP" → si valide, enregistre mfa_enabled=1 et redirige dashboard.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.php sans 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).
  • 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 (via config.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>
<html lang="fr">
<head><meta charset="UTF-8"><title>Setup MFA</title></head>
<body>
<h1>Activer MFA</h1>
<p>Scannez ce QR Code avec Google Authenticator :</p>
<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 "<p style='color:red'>$error</p>"; ?>
</body>
</html>

// ===== 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 "<p style='color:red'>$error</p>"; ?>
</body>
</html>

// ===== dashboard.php =====
require __DIR__ . '/config.php';
require_login();
if (!is_fully_authenticated()) {
    header('Location: mfa_verify.php');
    exit;
}
?>
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><title>Dashboard</title></head>
<body>
<h1>Bienvenue, <?= htmlspecialchars($_SESSION['user_email'] ?? '') ?></h1>
<p>Votre compte est protĂ©gĂ© par la MFA TOTP ✅</p>
<p><a href="logout.php">Se déconnecter</a></p>
</body>
</html>