Tutoriel
Partie 23 — Authentification : inscription et connexion utilisateur

Partie 23 — Authentification : inscription et connexion utilisateur

Crée une authentification PHP/MySQL sécurisée : inscription, login, password_hash/password_verify, sessions, logout, protection pages, validations et bonnes pratiques.

PHP 187 Mis à jour 3 hours ago
Conseil : lisez d’abord les sections clés, puis essayez un QCM lié à la même notion pour valider votre compréhension.

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 == → utiliser password_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)

  1. Ajouter une page “profil” (protected) qui affiche name/email/role.
  2. Ajouter une règle : seuls les “admin” accèdent à admin.php.
  3. Ajouter un champ “confirm password” à l’inscription + validation.
  4. Ajouter un “reset password” (token par email) (option avancée).
  5. 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.