Aujourd’hui, pour le jour 18 de #100JoursPourCoder, nous allons enrichir notre système d’inscription avec une validation par email. Cela signifie qu’un utilisateur ne pourra pas se connecter tant qu’il n’aura pas cliqué sur un lien reçu dans sa boîte mail. Ce lien contient un token de vérification unique, temporaire (valide 24h), et sécurisé. Ce mécanisme est essentiel pour s’assurer que l’adresse email fournie est bien réelle et appartenant au visiteur.
Nous allons voir ensemble, pas à pas, comment modifier notre contrôleur d’inscription, notre modèle User.php
, puis comment envoyer un mail avec PHPMailer (installé au jour 17), générer un token, et créer une route VerifyController
capable de gérer cette validation.
Chaque action, chaque ligne de code, sera expliquée clairement pour que tout le monde puisse suivre, même si vous débutez avec PHP, l’architecture MVC ou l’envoi d’emails.
- Générer un token de validation unique lors de l’inscription
- Envoyer le mail de validation avec PHPMailer
- Créer le contrôleur VerifyController pour activer le compte
- Méthodes à ajouter dans models/User.php
- Définir la route dans le routeur
- Résultat final
- Mise à jour du menu du haut dans le fichier header.php
- Redirection au niveau des contrôleurs
Générer un token de validation unique lors de l’inscription
Dans le code actuel de notre notre site web Créa-code, dans le contrôleur SigninController
, le champ verification_token
a pour valeur null
lors de la création du compte. Nous allons modifier cela.
Tout d’abord, dans la méthode register()
de SigninController
, juste après le hachage du mot de passe, ajoutons la génération d’un token de vérification.
Un token, c’est une longue chaîne unique, difficile à deviner, qui va servir d’identifiant temporaire. On peut utiliser la fonction bin2hex(random_bytes(32))
qui nous donne 64 caractères sécurisés.
Voici ce qu’on ajoute :
$verification_token = bin2hex(random_bytes(32));
$token_expiry = date('Y-m-d H:i:s', strtotime('+1 day')); // valide 24h
On a donc notre token et sa date d’expiration. On va maintenant les inclure dans la création de l’utilisateur, dans la méthode create()
du fichier app/Models/User.php. Modifions cet appel :
$userModel->create([
'email' => $email,
'password' => $hashedPassword,
'pseudo' => $pseudo,
...
'is_active' => 0,
'status' => 'actif',
'verification_token' => $verification_token,
'reset_token' => null,
'reset_token_expiry' => $token_expiry,
]);
Ce qu’on fait ici, c’est très simple :
- On indique que l’utilisateur n’est pas encore actif (
is_active = 0
) - On enregistre le token de validation dans
verification_token
- Et on utilise
reset_token_expiry
pour y stocker la date de péremption du lien (oui, ce champ est utilisé pour la réinitialisation du mot de passe, mais on l’utilise aussi ici pour éviter d’ajouter une colonne de plus).
Envoyer le mail de validation avec PHPMailer
Maintenant que notre utilisateur est enregistrer dans la base de données avec un token unique, il faut lui envoyer un mail contenant un lien pour vérifier son adresse mail. Pour cela, nous allons utiliser PHPMailer.
Dans votre SigninController
, juste après l’appel à $userModel->create()
, insérons le code suivant :
require_once '../vendor/autoload.php';
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
Puis, juste après la création de la classe :
try {
$mail->CharSet = 'UTF-8';
$mail->isSMTP();
$mail->Host = 'smtp.example.com'; // à adapter
$mail->SMTPAuth = true;
$mail->Username = 'votre_email@example.com'; // à adapter
$mail->Password = 'votre_mot_de_passe'; // à adapter
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
$mail->setFrom('monmail@gmail.com', 'Créa-code');
$mail->addAddress($email, $pseudo);
$mail->isHTML(true);
$mail->Subject = 'Validez votre compte sur Créa-code';
$mail->Body = "
<h1>Bienvenue sur Créa-code !</h1>
<p>Merci de vous être inscrit. Pour activer votre compte, veuillez cliquer sur ce lien :</p>
<p><a href='https://code.crea-troyes.fr/" . BASE_URL . "verify?token=$verification_token'>Activer mon compte</a></p>
<p>Ce lien est valable 24 heures.</p>
";
$mail->send();
} catch (Exception $e) {
if (DEVELOPMENT) {
echo "Erreur lors de l'envoi du mail : {$mail->ErrorInfo}";
exit;
}
}
// Création de la session utilisateur
$_SESSION['user'] = [
'pseudo' => $pseudo,
'email' => $email,
'role' => 'user',
'is_active' => 0
];
Ce code envoie un mail avec un lien de la forme :
https://code.crea-troyes.fr/verify?token=xyz123
L’utilisateur n’a plus qu’à cliquer dessus pour activer son compte.
Créer le contrôleur VerifyController
pour activer le compte
Maintenant que nous avons envoyé un lien contenant un token sécurisé, nous devons créer un contrôleur qui va traiter cette URL. Ce contrôleur vérifiera si le token reçu est valide, non expiré, et non déjà utilisé.
Nous allons créer un fichier VerifyController.php
dans le dossier controllers
.
Voici le contenu de base de ce fichier, que nous allons commenter ligne par ligne.
app/Controllers/VerifyController.php
:
<?php
namespace App\Controllers;
use App\Models\User;
use App\Core\Controller;
use App\Core\View;
use App\Core\Database;
class VerifyController extends Controller
{
public function index()
{
if (isset($_SESSION['user']) && $_SESSION['is_active'] == 1) {
header('Location: '.BASE_URL.'dashboard');
exit;
}
$title = "Vérification de votre email - Créa-code";
// On vérifie que le token est bien présent dans l'URL
if (!isset($_GET['token']) || empty($_GET['token'])) {
$errors =["Lien de validation invalide."];
View::render('login', compact('title', 'errors'));
return;
}
$token = htmlspecialchars($_GET['token']);
// On charge le modèle User
$userModel = new User(Database::getInstance());
// On récupère l'utilisateur correspondant à ce token
$user = $userModel->findByTokenVerif($token);
// Si aucun utilisateur trouvé, ou déjà activé, ou token expiré
if (!$user || $user['is_active'] == 1 || strtotime($user['reset_token_expiry']) < time()) {
$errors = ["Ce lien de validation est invalide ou expiré."];
View::render('login', compact('title', 'errors'));
return;
}
// Si tout est OK, on active le compte
$userModel->activateAccount($user['id']);
$errors = ["Votre compte a bien été activé. Vous pouvez vous connecter."];
View::render('login', compact('title', 'errors'));
return;
}
}
Prenons quelques instants pour expliquer tout ces lignes de code.
Étape 1 : Vérification du token dans l’URL
// On vérifie que le token est bien présent dans l'URL
if (!isset($_GET['token']) || empty($_GET['token'])) {
$errors =["Lien de validation invalide."];
View::render('login', compact('title', 'errors'));
return;
}
On s’assure que l’utilisateur est bien arrivé ici avec un paramètre ?token=...
. Si ce n’est pas le cas, on le redirige vers la page de connexion avec un message d’erreur.
Étape 2 : Sécurisation du token
$token = htmlspecialchars($_GET['token']);
On sécurise le contenu du token avec htmlspecialchars()
pour éviter d’éventuelles injections.
Étape 3 : Chargement du modèle User
$userModel = new User(Database::getInstance());
On inclut notre modèle User.php
, puis on crée une instance.

Des formations informatique pour tous !
Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…
Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.
Découvrez mes formations Qui suis-je ?Étape 4 : Recherche de l’utilisateur avec ce token
$user = $userModel->findByTokenVerif($token);
On suppose que le modèle User.php
contient une méthode findByToken()
qui retourne un utilisateur selon son verification_token
. On codera cette méthode dans un instant.
Étape 5 : Vérification de la validité du token
// Si aucun utilisateur trouvé, ou déjà activé, ou token expiré
if (!$user || $user['is_active'] == 1 || strtotime($user['reset_token_expiry']) < time()) {
$errors = ["Ce lien de validation est invalide ou expiré."];
View::render('login', compact('title', 'errors'));
return;
}
On vérifie plusieurs choses :
- Si aucun utilisateur ne correspond au token : alors c’est un faux lien
- Si le compte est déjà activé (
is_active == 1
) : alors ce lien est inutile - Si la date d’expiration est dépassée : alors ce lien est expiré
Étape 6 : Activation du compte
$userModel->activateAccount($user['id']);
On appelle une méthode activateAccount()
que nous allons coder dans User.php
. Elle doit mettre is_active
à 1
, et supprimer le token.
Étape 7 : Message de succès et redirection
$errors = ["Votre compte a bien été activé. Vous pouvez vous connecter."];
View::render('login', compact('title', 'errors'));
return;
Le compte est maintenant actif. L’utilisateur est redirigé vers la page de connexion, avec un message de confirmation.
On utilise la variable
$errors
alors qu’il ne s’agit pas d’une erreur. Cela peut paraître paradoxal mais de cette façon que nous pouvons transmettre et afficher un message dans la vuelogin.php
:
<h1>Connexion</h1>
<?php if (!empty($errors)) : ?>
<div style="color:red;">
<?php foreach ($errors as $error) : ?>
<p><?= htmlspecialchars($error) ?></p>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form id="login-form" action="<?= BASE_URL ?>login/post" method="post">
<label for="email">Email :</label><br>
<input type="email" name="email" id="email" required>
<div id="email-error" class="error-message"></div>
<label for="password">Mot de passe :</label><br>
<input type="password" name="password" id="password" required>
<div id="password-error" class="error-message"></div>
<button type="submit">Se connecter</button>
</form>
<p>Pas encore inscrit ? <a href="<?= BASE_URL ?>signin">Créer un compte</a></p>
<p><a href="<?= BASE_URL ?>forgot">Mot de passe oublié ?</a></p>
<script src="<?= BASE_URL ?>assets/js/login-form-validation.js" defer></script>
Méthodes à ajouter dans models/User.php
Nous devons maintenant ajouter deux méthodes à notre modèle utilisateur app/Models/User.php :
findByTokenVerif($token)
: pour retrouver un utilisateur via son tokenactivateAccount($id)
: pour mettre à jour la valeur deis_active
, supprimer le token et nettoyer les colonnes
Méthode findByToken($token)
public function findByTokenVerif($token)
{
$sql = "SELECT * FROM users WHERE verification_token = :token LIMIT 1";
$stmt = $this->db->prepare($sql);
$stmt->bindValue(':token', $token, PDO::PARAM_STR);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
Ce code prépare une requête SQL pour rechercher un utilisateur dont le verification_token
correspond au paramètre. On ne prend qu’un seul résultat.
Méthode activateAccount($id)
public function activateAccount($id)
{
$sql = "UPDATE users SET is_active = 1, verification_token = NULL, reset_token_expiry = NULL WHERE id = :id";
$stmt = $this->db->prepare($sql);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}
Ici, on met is_active
à 1, on supprime le token et sa date d’expiration pour qu’il ne soit plus utilisable.
Définir la route dans le routeur
Pour que l’URL https://code.crea-troyes.fr/verify?token=xyz123
fonctionne, il faut que notre routeur reconnaisse le mot verify
.
// Vérification de l'email
$router->get('/verify', 'VerifyController@index');
Cela permet d’exécuter VerifyController@index
.
Résultat final
Quand l’utilisateur s’inscrit :
- Un token est généré et stocké en base de données
- Un mail est envoyé avec un lien contenant ce token
- L’utilisateur clique, est redirigé vers le contrôleur
VerifyController
- Si le token est valide et non expiré, le compte est activé
- Sinon, il est redirigé vers la page de login avec un message d’erreur
Mise à jour du menu du haut dans le fichier header.php
Il va maintenant falloir mettre à jour la vue du menu du haut pour qu’elle affiche un menu correspondant à la situation du visiteur. Inutile de le considérer comme connecté si son adresse mail n’est pas validée (si is_active = 0
).
Dans le fichier app/Views/partials/header.php :
<header>
<h1><a href="<?= BASE_URL ?>">Créa-code</a></h1>
<nav>
<a href="<?= BASE_URL ?>">Accueil</a>
<?php if (isset($_SESSION['user']) && $_SESSION['user']['is_active'] == 1): ?>
<a href="<?= BASE_URL ?>dashboard">Dashboard</a>
<a href="<?= BASE_URL ?>logout">Déconnexion</a>
<?php elseif (isset($_SESSION['user']) && $_SESSION['user']['is_active'] == 0): ?>
<a href="<?= BASE_URL ?>login">Connexion</a>
<?php else: ?>
<a href="<?= BASE_URL ?>signin">Inscription</a>
<a href="<?= BASE_URL ?>login">Connexion</a>
<?php endif; ?>
</nav>
</header>
Redirection au niveau des contrôleurs
Pour éviter qu’un utilisateur se connecte à son Dashboard alors que son adresse n’est pas vérifiée, il va nous falloir tester la valeur de is_active
.
Dans le fichier app/Controllers/DashboardController.php :
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Core\View;
class DashboardController extends Controller
{
public function index()
{
if (!isset($_SESSION['user'])) {
header('Location: '.BASE_URL.'login');
exit;
}
if ($_SESSION['user']['is_active'] == 0) {
$_SESSION['login_errors'] = ["Votre compte n'est pas encore activé. Veuillez vérifier votre email."];
header('Location: '.BASE_URL.'login');
exit;
}
$title = "Dashboard - Créa-code";
$desc = "Dashboard - Créa-code";
View::render('dashboard/dashboard', compact('title', 'desc'));
}
}
Idem pour le fichier app/Controller/LoginController.php :
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Core\View;
use App\Models\User;
use App\Core\Database;
class LoginController extends Controller
{
public function index(): void
{
if (isset($_SESSION['user']) && $_SESSION['user']['is_active'] == 1) {
header('Location: '.BASE_URL.'dashboard');
exit;
}
$title = "Login - Créa-code";
$desc = "Login - Créa-code";
$errors = $_SESSION['login_errors'] ?? [];
unset($_SESSION['login_errors']);
View::render('login', compact('title', 'desc', 'errors'));
}
public function login()
{
if (isset($_SESSION['user'])) {
header('Location: '.BASE_URL.'dashboard');
exit;
}
$errors = [];
// Vérifier si le formulaire est bien soumis
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$password = trim($_POST['password'] ?? '');
// Vérification des champs requis
if (empty($email) || empty($password)) {
$errors[] = "Tous les champs sont obligatoires.";
}
// Vérification du format de l'email
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "L'adresse email est invalide.";
}
// Si aucune erreur, on passe à la suite
if (empty($errors)) {
$userModel = new User(Database::getInstance());
$user = $userModel->findByEmail($email);
if ($user && password_verify($password, $user['password'])) {
// Connexion réussie
$_SESSION['user'] = [
'id' => $user['id'],
'email' => $user['email'],
'pseudo' => $user['pseudo'],
'role' => $user['role'],
'is_active' => $user['is_active']
];
header('Location: '.BASE_URL.'dashboard');
exit;
} else {
$errors[] = "Identifiants incorrects.";
}
}
}
if (!empty($errors)) {
$_SESSION['login_errors'] = $errors;
header('Location: ' . BASE_URL . 'login');
exit;
}
}
}
Nous venons de mettre en place une fonctionnalité indispensable pour tout site moderne : l’activation d’un compte par lien de confirmation email. C’est une étape essentielle pour garantir la sécurité, vérifier la bonne adresse du visiteur, et éviter les inscriptions frauduleuses.
Grâce à ce mécanisme, nous assurons que seuls les utilisateurs qui ont confirmé leur adresse mail peuvent accéder aux fonctionnalités réservées. Cela renforce la fiabilité de notre plateforme, sécurise les accès, et améliore l’expérience utilisateur.
Ce système d’activation de compte par email est aujourd’hui incontournable. Il protège votre application, et rassure vos membres.
La suite, demain …