Partie 23 — Authentification : inscription et connexion utilisateur
À partir du moment où ton site a des utilisateurs (profil, dashboard, historique, favoris, admin…), tu as besoin d’une authentification : inscription + connexion. Et si tu fais ça “à l’ancienne” (mots de passe en clair, SQL concaténé, sessions mal gérées), tu crées une faille grave.
Ici, on va construire un système d’authentification sécurisé en PHP + MySQL,
avec PDO et les fonctions natives recommandées :
password_hash() et password_verify().
👉 Objectif : un mini-module “auth” réutilisable dans tes projets : register/login/logout + protection des pages + messages flash + bonnes pratiques.
1) Base de données : table users (propre)
Structure minimale (tu peux ajouter avatar, phone, role, etc.) :
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
email VARCHAR(190) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role ENUM('user','admin') NOT NULL DEFAULT 'user',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
✅ Important : on stocke password_hash, jamais le mot de passe en clair.
2) Fichiers de base : db.php + helpers.php
Reprends la connexion PDO des parties précédentes (db.php).
Pour l’auth, on a besoin de :
- sessions
- flash messages
- CSRF token (sur les formulaires)
- helpers de sécurité (escape HTML)
⚠️ Si tu utilises déjà helpers.php (Partie 21), garde le même.
Ici je rappelle juste l’essentiel : session + e() + flash() + csrf_token().
3) Fonctions DB : trouver un user + créer un user (PDO sécurisé)
On centralise les requêtes dans auth_repo.php.
<?php
// auth_repo.php
require_once __DIR__ . "/db.php";
function user_find_by_email(string $email): ?array
{
$stmt = db()->prepare("SELECT id, name, email, password_hash, role FROM users WHERE email = :e LIMIT 1");
$stmt->execute(["e" => $email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
function user_find_by_id(int $id): ?array
{
$stmt = db()->prepare("SELECT id, name, email, role FROM users WHERE id = :id LIMIT 1");
$stmt->execute(["id" => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
function user_create(string $name, string $email, string $passwordHash): int
{
$stmt = db()->prepare("
INSERT INTO users (name, email, password_hash, role, created_at)
VALUES (:n, :e, :p, 'user', NOW())
");
$stmt->execute(["n" => $name, "e" => $email, "p" => $passwordHash]);
return (int)db()->lastInsertId();
}
?>
Note : on sépare bien password_hash (DB) et password (input).
4) Inscription : validations + email unique
Sur l’inscription, tu dois au minimum valider :
- name non vide
- email valide
- password assez long
- email pas déjà pris (unique)
4.1 register.php (form + traitement)
<?php
// register.php
require_once __DIR__ . "/helpers.php";
require_once __DIR__ . "/auth_repo.php";
$errors = [];
$old = ["name"=>"", "email"=>""];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
csrf_check($_POST["csrf"] ?? "");
$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$pass = (string)($_POST["password"] ?? "");
$old["name"] = $name;
$old["email"] = $email;
if ($name === "") $errors["name"] = "Nom obligatoire.";
if ($email === "" || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors["email"] = "Email invalide.";
if (mb_strlen($pass) < 8) $errors["password"] = "Mot de passe trop court (8 min).";
if (!$errors) {
if (user_find_by_email($email)) {
$errors["email"] = "Email déjà utilisé.";
}
}
if (!$errors) {
$hash = password_hash($pass, PASSWORD_DEFAULT);
$id = user_create($name, $email, $hash);
// login auto après inscription
session_regenerate_id(true);
$_SESSION["user_id"] = $id;
flash("success", "Compte créé. Bienvenue !");
header("Location: dashboard.php");
exit;
}
}
?>
<div style="font-family:Tahoma; max-width:860px; margin:0 auto;">
<h2>Inscription</h2>
<form method="POST">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<label>Nom</label><br>
<input name="name" value="<?= e($old["name"]) ?>" style="width:100%;">
<div style="color:#b91c1c;"><?= e($errors["name"] ?? "") ?></div>
<br>
<label>Email</label><br>
<input name="email" value="<?= e($old["email"]) ?>" style="width:100%;">
<div style="color:#b91c1c;"><?= e($errors["email"] ?? "") ?></div>
<br>
<label>Mot de passe</label><br>
<input type="password" name="password" style="width:100%;">
<div style="color:#b91c1c;"><?= e($errors["password"] ?? "") ?></div>
<br><br>
<button>Créer le compte</button>
<a href="login.php" style="margin-left:10px;">J’ai déjà un compte</a>
</form>
</div>
✅ On utilise password_hash() : PHP choisit l’algorithme adapté et gère le “salt” automatiquement.
5) Connexion : password_verify + session sécurisée
Login = tu récupères le user par email, puis tu compares le mot de passe
avec password_verify(). Jamais de comparaison directe.
5.1 login.php
<?php
// login.php
require_once __DIR__ . "/helpers.php";
require_once __DIR__ . "/auth_repo.php";
$errors = [];
$old = ["email"=>""];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
csrf_check($_POST["csrf"] ?? "");
$email = trim($_POST["email"] ?? "");
$pass = (string)($_POST["password"] ?? "");
$old["email"] = $email;
if ($email === "" || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors["email"] = "Email invalide.";
}
if ($pass === "") {
$errors["password"] = "Mot de passe obligatoire.";
}
if (!$errors) {
$user = user_find_by_email($email);
if (!$user) {
$errors["email"] = "Email ou mot de passe incorrect.";
} else {
if (!password_verify($pass, $user["password_hash"])) {
$errors["email"] = "Email ou mot de passe incorrect.";
}
}
}
if (!$errors) {
// Important : empêcher fixation de session
session_regenerate_id(true);
$_SESSION["user_id"] = (int)$user["id"];
flash("success", "Connexion réussie.");
header("Location: dashboard.php");
exit;
}
}
?>
<div style="font-family:Tahoma; max-width:860px; margin:0 auto;">
<h2>Connexion</h2>
<?php if ($m = flash("success")): ?>
<div style="padding:12px;background:#ecfeff;border-left:4px solid #06b6d4;margin:12px 0;">
<?= e($m) ?>
</div>
<?php endif; ?>
<form method="POST">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<label>Email</label><br>
<input name="email" value="<?= e($old["email"]) ?>" style="width:100%;">
<div style="color:#b91c1c;"><?= e($errors["email"] ?? "") ?></div>
<br>
<label>Mot de passe</label><br>
<input type="password" name="password" style="width:100%;">
<div style="color:#b91c1c;"><?= e($errors["password"] ?? "") ?></div>
<br><br>
<button>Se connecter</button>
<a href="register.php" style="margin-left:10px;">Créer un compte</a>
</form>
</div>
Remarque importante : le message d’erreur est volontairement “vague” (“Email ou mot de passe incorrect”) pour ne pas aider un attaquant à deviner quels emails existent.
6) Protéger des pages : require_login()
Une fois connecté, tu veux protéger des pages comme dashboard.php.
<?php
// auth_guard.php
require_once __DIR__ . "/helpers.php";
require_once __DIR__ . "/auth_repo.php";
function auth_user(): ?array
{
$id = (int)($_SESSION["user_id"] ?? 0);
return $id > 0 ? user_find_by_id($id) : null;
}
function require_login(): array
{
$user = auth_user();
if (!$user) {
flash("success", "Connecte-toi pour accéder à cette page.");
header("Location: login.php");
exit;
}
return $user;
}
?>
Utilisation dans dashboard.php :
<?php require_once __DIR__ . "/auth_guard.php"; $user = require_login(); ?> <h2>Dashboard</h2> <p>Bienvenue <?= e($user["name"]) ?> !</p>
7) Déconnexion (logout) : propre et sécurisée
Logout = supprimer l’info de session, et idéalement régénérer.
<?php
// logout.php
require_once __DIR__ . "/helpers.php";
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$p = session_get_cookie_params();
setcookie(session_name(), "", time()-3600, $p["path"], $p["domain"], $p["secure"], $p["httponly"]);
}
session_destroy();
header("Location: login.php");
exit;
?>
✅ En vrai projet, tu peux aussi logger l’événement (date, IP, user id) côté serveur.
8) Option “Remember me” (cookie) — le concept propre
Beaucoup ajoutent “remember me” et le font mal (cookie = user_id en clair). La bonne méthode (concept) :
- générer un token long aléatoire
- stocker un hash du token en DB (pas le token en clair)
- mettre le token en cookie HttpOnly + Secure
- au retour, vérifier le token, régénérer, et renouveler
⚠️ Si tu veux, je peux te faire une Partie “Remember me” complète en bonus (table remember_tokens + rotation). Ici je garde la Partie 23 concentrée sur register/login standard.
9) Erreurs fréquentes en authentification
- Stocker le password en clair → jamais
- Comparer des mots de passe avec
==→ utiliserpassword_verify - Pas de session_regenerate_id → risque de fixation de session
- Messages d’erreur trop précis (“email existe / n’existe pas”) → fuite d’info
- Oublier CSRF sur formulaires → risque
- Afficher les erreurs DB en production → fuite de détails serveur
✅ Si tu appliques tout ce qu’on a fait : tu as une auth simple mais sérieuse.
10) Exercices (niveau projet)
- Ajouter une page “profil” (protected) qui affiche name/email/role.
- Ajouter une règle : seuls les “admin” accèdent à
admin.php. - Ajouter un champ “confirm password” à l’inscription + validation.
- Ajouter un “reset password” (token par email) (option avancée).
- Logger les tentatives de login (table login_attempts) (option pro).
Conclusion
Tu as maintenant un module d’authentification complet en PHP : inscription, connexion, sessions, protection des pages, logout, et bonnes pratiques de sécurité. C’est une base solide pour construire un vrai site.
Partie 24 logique : Rôles & permissions (admin/user + guards), ou Sécurité web (XSS/CSRF/SQLi + uploads) en checklist complète.