3.8 KiB
3.8 KiB
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
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=<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=<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 sessionreturn_tofiltré : 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 :
$magicUrl = url('/login/magic.php') . '?token=' . urlencode($token);
/* envoyer_mail_smtp(...) ou mail(...) */