# Authentification — Lien magique Mécanisme d'authentification par email sans mot de passe. L'utilisateur reçoit un lien à usage unique, valide un temps limité, qui ouvre une session PHP. ## Fichiers concernés | Fichier | Rôle | |---|---| | `public/login/index.php` | Formulaire de demande + génération du token | | `public/login/magic.php` | Consommation du token + ouverture de session | | Table BDD `auth_magic_links` | Persistance des tokens | ## Configuration (`.env`) | Variable | Défaut | Description | |---|---|---| | `MAGIC_LINK_TTL_MINUTES` | `30` | Durée de validité du lien | | `MAGIC_COOLDOWN_MINUTES` | `5` | Délai minimal entre deux demandes pour le même email | | `MAGIC_WINDOW_HOURS` | `12` | Fenêtre glissante pour le plafond | | `MAGIC_MAX_PER_WINDOW` | `5` | Nombre maximal de liens émis par fenêtre | ## Schéma BDD — `auth_magic_links` ```sql id UUID PRIMARY KEY (gen_random_uuid()) email TEXT NOT NULL token TEXT NOT NULL UNIQUE created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() expires_at TIMESTAMPTZ NOT NULL consumed_at TIMESTAMPTZ NULL -- NULL = non consommé ip TEXT user_agent TEXT return_to TEXT NULL -- chemin relatif de redirection post-login ``` ## Phase 1 — Demande du lien (`index.php`, POST) ``` [Formulaire email] → POST /login │ ├─ Validation CSRF ├─ Validation syntaxe email │ ├─ Purge BDD : DELETE liens expirés ou consommés pour cet email │ ├─ Cooldown : un lien < MAGIC_COOLDOWN_MINUTES → refus ├─ Plafond : >= MAGIC_MAX_PER_WINDOW liens dans MAGIC_WINDOW_HOURS → refus │ ├─ Génération token : random_bytes(32) → base64url (43 chars, URL-safe) ├─ INSERT auth_magic_links (expires_at = NOW() + TTL) │ └─ [stub] Envoi email : /login/magic.php?token= ``` Le token est un base64url sans padding (`+/` remplacés par `-_`, `=` supprimés) — il ne contient que des caractères `[A-Za-z0-9\-_]`. L'envoi de l'email est actuellement un **stub** : le code construit `$magicUrl` mais l'appel SMTP n'est pas implémenté (`index.php:142`). ## Phase 2 — Consommation du lien (`magic.php`, GET) ``` [Clic sur le lien] → GET /login/magic.php?token= │ ├─ Validation format token (regex [A-Za-z0-9\-\_]) │ ├─ BEGIN TRANSACTION │ ├─ SELECT ... FOR UPDATE (verrou anti double-consommation) │ ├─ Token inconnu → 400 │ ├─ consumed_at non null → "Lien déjà utilisé" │ ├─ expires_at dépassé → "Lien expiré" │ ├─ UPDATE consumed_at = NOW() │ └─ COMMIT │ ├─ session_start() + session_regenerate_id(true) ├─ $_SESSION['user_email'] = email │ └─ Redirection 303 → return_to (validé : doit commencer par /) ``` Le `FOR UPDATE` garantit qu'un token ne peut pas être consommé deux fois en cas de double-clic ou de requêtes concurrentes. ## Sécurités notables - **CSRF** sur le formulaire de demande - **Rate limiting** double (cooldown + plafond glissant) par email - **Token URL-safe** généré par CSPRNG (`random_bytes`) - **Verrou transactionnel** (`FOR UPDATE`) à la consommation - **`session_regenerate_id(true)`** — prévient la fixation de session - **`return_to` filtré** : seuls les chemins relatifs (commençant par `/`) sont acceptés ## État actuel — ce qui reste à câbler L'envoi SMTP n'est pas implémenté. Le token est correctement généré et persisté en BDD, mais le mail n'est pas envoyé. Voir `public/login/index.php:142` : ```php $magicUrl = url('/login/magic.php') . '?token=' . urlencode($token); /* envoyer_mail_smtp(...) ou mail(...) */ ```