Comment gérer les utilisateurs et la double-authentication comme un pro

8 min de lecture

Évitez les hackers avec la double-authentification en POO. Une méthode infaillible, expliquée avec humour et des codes QR.

Si vous lisez ce billet, c'est que vous en avez plein le dos des failles de sécurité à la con et que vous voulez protéger votre site contre ces gentils hackers. On va voir ensemble comment le code dans src/Command/User comment j'ai géré les utilisateurs et leur authentification à deux facteurs.

Si t'as l'attention d'un poisson rouge cocaïnomane, y'a des titres et des sous-titres pour t'y retrouver.

La base indispensable pour pas se faire trouer

Aujourd'hui, protéger un site avec juste un mot de passe, c'est comme rouler sans freins - ça donne une fausse impression de sécurité avant de planter royalement ton site. C'est là qu'intervient l'authentification à deux facteurs (2FA), cette petite merveille qui demande, en plus de ton mot de passe, un code temporaire généré par une app sur ton téléphone.

J'ai mis en place trois classes et deux commandes :

  1. UserCreateCommand : Crée un utilisateur en l'insérant dans la base de données
  2. User2FaSetupCommand : Configure la double-authentification pour un utilisateur existant
  3. AbstractUserQrCommand : Une classe abstraite (oui, abstraite, car on fait de la POO comme des grands) qui factorise le code commun

Dépendances externes : le bordel qu'on importe mais qu'on lit jamais

Avant d'entrer dans ces commandes en détail, on va voir les dépendances dont que j'ai utilisées :

OTPHP

OTPHP c'est une librairie qui permet de gérer ces mots de passe à usage unique basés sur le temps (TOTP). C'est le standard pour la 2FA, utilisé par Google Authenticator, Authy, et toutes ces merdouilles.

use OTPHP\TOTP;

$totp = TOTP::create();
$totp->setLabel('utilisateur@merde.com');
$totp->setIssuer('TonSiteDeMerde');

Voilà, en trois lignes de code, t'as créé un secret TOTP. Bravo champion.

Endroid/QrCode

Cette classe génère des QR codes. Parce que demander à un utilisateur de taper un secret à 32 caractères à la main, c'est comme demander à un âne de résoudre une équation différentielle.

$qrCode = new QrCode(
    data: $uri,
    encoding: new Encoding('UTF-8'),
    errorCorrectionLevel: ErrorCorrectionLevel::Medium,
    size: 300,
    margin: 10,
    roundBlockSizeMode: RoundBlockSizeMode::Margin,
    foregroundColor: new Color(0, 0, 0),
    backgroundColor: new Color(255, 255, 255),
);

Symfony/Cache

Le système de cache de Symfony, utilisé ici pour stocker les QR codes temporaires. Ouais, on les stocke pas en mémoire comme des porcs, on utilise le système de fichiers.

$cache = new FilesystemAdapter(
    namespace: 'user_qr_codes',
    defaultLifetime: 3600,
    directory: $this->cacheDirectory,
);

Pourquoi 3600 secondes (1 heure) ? Parce que si t'arrives pas à scanner un QR code en une heure, t'as probablement des problèmes plus graves que la sécurité informatique.

Scheb/2fa-bundle

Le bundle de double authentification pour Symfony. C'est ce qui permet à notre User d'implémenter les interfaces TwoFactorInterface et TotpConfigurationInterface. Sans lui, on serait bien dans la merde.

Symfony/Console et ses potes

Tous les potes de Symfony pour faire des commandes Console avec des couleurs et des options. C'est grâce à cette bande qu'on peut avoir des inputs interactifs et des outputs qui ressemblent pas à une bouillie de caractères ASCII.

Architecture globale : comment ce bordel tient debout

Voici l'architecture de mon système, expliquée comme si vous aviez 5 ans mais avec le vocabulaire d'un docker sur les quais :

  1. AbstractUserQrCommand : La classe de base qui contient tout le bordel pour générer des QR codes, les stocker dans le cache, et configurer le TOTP. Elle s'appuie sur le service dédié TotpQrCodeService.

  2. UserCreateCommand : Hérite de la classe précédente. Elle te demande toutes les infos nécessaires pour créer un utilisateur, vérifie qu'il existe pas déjà, puis te propose de configurer la 2FA directement.

  3. User2FaSetupCommand : Aussi héritée de la classe abstraite. Elle cherche un utilisateur par son nom, puis configure ou reconfigure sa 2FA.

  4. TotpQrCodeService : Un service dédié qui s'occupe de créer les TOTP et les QR codes. Je l'a mis dans un service pour pas se répéter comme des abrutis.

  5. Entité User : Mon modèle d'utilisateur qui implémente toutes les interfaces requises pour la 2FA, avec des propriétés pour stocker le secret TOTP et son état d'activation.

Franchement, c'est bien foutu cette merde (ouais auto promo). Ça démontre une architecture orientée objet propre, avec séparation des responsabilités.

Classe par classe : analyse profonde de ce bordel

AbstractUserQrCommand

Cette classe abstraite factorise tout le code lié à la génération de codes QR pour la 2FA. Elle s'appuie sur plusieurs constantes :

private const string QR_CODE_CACHE_NAMESPACE = 'user_qr_codes';
private const int QR_CODE_CACHE_LIFETIME = 3600;
private const string QR_CODE_CACHE_PREFIX = 'qrcode_';
private const string TOTP_ISSUER = 'Lecodeestdanslepre';

Le constructeur prend trois dépendances :

  • EntityManagerInterface : Pour persister les changements
  • TotpQrCodeService : Pour générer les TOTP et QR codes
  • cacheDirectory : Injecté via l'attribut #[Autowire], c'est le répertoire où sont stockés les fichiers de cache

La méthode principale generateTotpQrCode() :

  1. Configure le TOTP pour l'utilisateur
  2. Génère les données du QR code
  3. Sauvegarde ces données dans le cache
  4. Met à jour l'utilisateur en DB
  5. Affiche les infos à l'utilisateur
  6. Retourne le chemin du fichier QR code (je modifierai ça pour créer une route qui affichera le QR Code)

La méthode cleanQrCodes() nettoie tous les anciens QR codes d'un utilisateur.

TotpQrCodeService

Ce service encapsule la logique de génération des TOTP et QR codes. Il a deux méthodes publiques :

  • configureTotpForUser() : Crée un objet TOTP avec l'email de l'utilisateur comme label
  • generateQrCodeData() : Génère les données binaires du QR code au format PNG

Et une méthode privée createQrCode() qui configure l'objet QR code avec tous les paramètres nécessaires.

UserCreateCommand

Cette commande permet de créer un utilisateur de A à Z. Elle demande interactivement le nom d'utilisateur, l'email, éventuellement prénom et nom, le mot de passe et le rôle.

$username = $io->ask(
    question: 'Please enter the username',
    validator: function ($answer) {
        if (empty($answer)) {
            throw new \RuntimeException('Le nom d\'utilisateur ne peut pas être vide.');
        }

        return $answer;
    },
);

Notez la validation en ligne qui empêche les valeurs vides, façon badass.

La commande vérifie ensuite l'unicité du nom d'utilisateur et de l'email, puis crée l'utilisateur et le stocke en base. Enfin, elle propose de configurer directement la 2FA.

User2FaSetupCommand

Cette commande prend en paramètre un nom d'utilisateur, récupère l'utilisateur correspondant, puis propose :

  1. De mettre à jour ses infos (prénom, nom)
  2. De configurer ou reconfigurer sa 2FA

Les méthodes sont les mêmes que pour la commande de création, mais appliquées à un utilisateur existant.

Entity User

Notre entité User est au cœur du système. En plus des champs classiques (id, username, email, etc.), elle possède :

  • totpSecret : Le secret TOTP stocké (peut être null)
  • totpEnabled : Un booléen qui indique si la 2FA est activée

Elle implémente TwoFactorInterface via plusieurs méthodes :

  • isTotpAuthenticationEnabled() : Retourne si la 2FA est activée
  • getTotpAuthenticationUsername() : Retourne l'identifiant pour la 2FA
  • getTotpAuthenticationConfiguration() : Retourne une classe anonyme qui implémente TotpConfigurationInterface

Cette dernière méthode est particulièrement intéressante :

public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface
{
    // Si l'authentification TOTP n'est pas activée ou pas configurée, retourner null
    if (!$this->totpEnabled || null === $this->totpSecret) {
        return null;
    }

    // Créer un objet configuration TOTP
    return new class($this->totpSecret) implements TotpConfigurationInterface {
        public function __construct(private readonly string $secret)
        {
        }

        public function getSecret(): string
        {
            return $this->secret;
        }

        public function getAlgorithm(): string
        {
            return 'sha1'; // Algorithme standard pour TOTP
        }

        public function getPeriod(): int
        {
            return 30; // Période standard de 30 secondes
        }

        public function getDigits(): int
        {
            return 6; // Nombre standard de chiffres
        }
    };
}

Elle utilise une classe anonyme (PHP 7+) pour implémenter l'interface requise, tout en encapsulant les détails d'implémentation.

Le mot de la fin

Cette architecture a plusieurs avantages :

  1. Séparation des responsabilités : Chaque classe a un rôle bien défini
  2. Réutilisation du code : L'héritage de AbstractUserQrCommand évite la duplication
  3. Service dédié : TotpQrCodeService encapsule la logique de génération de QR codes
  4. Cache intelligent : Les QR codes sont stockés temporairement avec nettoyage automatique

Bref, protégez-vous, parce que sur internet, si vous êtes pas paranos, c'est que vous êtes pas assez informés.

Liens utiles

Confidentialité

Ce site utilise Umami pour analyser le trafic de manière anonyme. Acceptez-vous la collecte de données anonymes ?