La sécurité en couches — pourquoi une défense en profondeur
Cette page n'explique pas comment configurer l'authentification de telaria. Elle explique pourquoi elle est construite en plusieurs couches indépendantes, et ce que chaque couche coûte et rapporte. Le fil directeur est un principe unique : ne jamais faire reposer la sécurité d'un système sur un seul point de contrôle.
Le principe : plusieurs barrières, aucune indispensable seule
Une barrière unique a une propriété désagréable : le jour où elle cède, tout cède. La défense en profondeur renverse ce pari. On empile des protections dont chacune suppose que la précédente a peut-être échoué. Si l'authentification est contournée, la limitation des rôles reste. Si une entité est sérialisée par erreur, la liste blanche des champs exposés reste. Aucune couche n'est la sécurité ; c'est leur superposition qui l'est.
Ce choix a un coût, et il faut le nommer. Chaque couche ajoute de la surface de code à écrire, à tester et à maintenir. Elle ajoute parfois une friction pour l'utilisateur légitime. On accepte ce coût parce que l'alternative — la barrière unique — concentre tout le risque en un seul endroit, et que ce endroit finit toujours par être trouvé.
Les sections qui suivent décrivent cinq couches. Elles ne se remplacent pas : elles se complètent, et c'est délibéré.
Couche 1 — l'authentification par token haché
L'accès programmatique (l'API) s'appuie sur un jeton porté par la requête. La décision structurante n'est pas d'utiliser un jeton — c'est de ne jamais stocker sa valeur en clair. Le système conserve uniquement une empreinte cryptographique (un hachage) du jeton ; à chaque requête, il recalcule l'empreinte de ce qui est présenté et la compare à celle qui est stockée.
Le raisonnement tient à un scénario précis : la compromission de la base de données. Si les jetons y étaient en clair, une fuite de base donnerait directement des accès valides. En ne stockant que l'empreinte, une fuite de base ne livre rien d'utilisable : on ne remonte pas d'une empreinte au jeton d'origine. Le jeton en clair n'existe qu'à un seul instant — sa génération — où il est affiché une fois, puis jamais reconservé.
Deux choix secondaires en découlent, et méritent d'être explicités :
- Un seul canal d'authentification accepté. Le système ne reconnaît qu'une manière précise de présenter le jeton et rejette toutes les autres. Multiplier les formats acceptés « pour être tolérant » multiplie la surface d'attaque et les chemins à tester. La tolérance est ici une faiblesse, pas une commodité.
- Un modèle opaque assumé. Le jeton ne porte aucune information ; il n'est qu'une clé de recherche vers un compte. Des mécanismes plus riches (jetons signés auto-portants, standards de délégation) existent et résoudraient d'autres problèmes — mais ils ne sont pas nécessaires ici. Choisir le mécanisme le plus simple qui répond au besoin est en soi une décision de sécurité : moins de pièces mobiles, moins de manières de se tromper.
Couche 2 — la double authentification, activable mais non imposée
Un second facteur (un code envoyé par courriel) peut s'ajouter au mot de passe. Le point intéressant n'est pas la 2FA elle-même mais la manière dont elle est activée, à deux niveaux distincts.
Au niveau du système, le mécanisme est disponible globalement. Au niveau de chaque compte, il est opt-in : par défaut, aucun verrouillage supplémentaire. Ce choix équilibre deux exigences en tension. Imposer la 2FA à tous maximise la protection mais dégrade l'accès des comptes à faible enjeu et augmente le risque de blocage. La laisser disponible et choisie place le curseur là où l'utilisateur — ou l'administration — juge l'enjeu réel.
Cette séparation en deux interrupteurs — un global, un par compte — est elle-même une couche. Elle permet, sans redéploiement, soit de renforcer l'ensemble du système d'un coup, soit d'ajuster un compte particulier. La flexibilité opérationnelle est ici au service de la sécurité : une protection qu'on peut activer vite, quand la menace change, vaut mieux qu'une protection figée dans le code.
Un dernier arbitrage concerne les appareils de confiance. Ne pas redemander le second facteur sur un appareil déjà validé réduit la friction. Mais un appareil de confiance qui ne peut jamais être révoqué devient un trou permanent. La décision est donc de rendre cette confiance révocable côté serveur : on peut, à tout moment, invalider les appareils d'un compte — ou de tous. Une commodité qui ne peut pas être reprise n'est pas une commodité, c'est une faille en attente.
Couche 3 — le pré-production « public dans le privé »
Un environnement de pré-production sert à valider ce qui ressemble au site public. Le problème : il ressemble au public, donc ses pages sont conçues pour être ouvertes — alors qu'à ce stade elles ne doivent l'être pour personne d'extérieur.
La réponse est une couche transverse qui s'active en amont de toute la logique applicative : quand elle est enclenchée, l'intégralité du site exige une authentification d'accès générique avant même d'atteindre la moindre page. Les pages « publiques » deviennent alors réellement privées, sans toucher à leur code. On ne rend pas chaque page privée une par une — on place une porte unique devant tout, qu'on ouvre ou ferme d'un seul geste selon l'environnement.
Deux détails de conception révèlent la discipline sous-jacente :
- L'échec doit échouer proprement. Une tentative d'accès avec des identifiants malformés est traitée de façon défensive : le système refuse sans produire d'erreur interne, sans laisser filtrer d'indice sur son fonctionnement. Un message d'erreur technique est déjà une information offerte à l'attaquant ; la porte se contente de rester fermée.
- Les exemptions sont minimales et exactes. Un point d'entrée qui porte déjà sa propre authentification peut être exempté de cette porte — mais l'exemption vise ce point précis, pas une famille d'adresses voisines. Élargir une exemption « par confort » crée un chemin de contournement. La règle : n'exempter que ce qui est strictement nécessaire, et de la manière la plus étroite possible.
Couche 4 — l'inscription fermée qui répond 404, pas 403
Le système sait créer des comptes, mais l'inscription publique est fermée par défaut. Le choix remarquable est la nature du refus : une ressource fermée répond « cette page n'existe pas » (404), et non « cette page existe mais vous êtes refusé » (403).
La distinction n'est pas cosmétique. Un 403 confirme l'existence de la fonctionnalité et invite à chercher comment la débloquer. Un 404 ne confirme rien : du point de vue de l'extérieur, la fonctionnalité n'existe simplement pas. On ne défend pas seulement l'accès, on retire l'information de sa présence. C'est une application directe du principe selon lequel un système en révèle le moins possible sur lui-même.
Le raisonnement de fond est un choix produit devenu choix de sécurité. En première version, les comptes sont créés par l'administration, jamais par auto-inscription anonyme. Une inscription publique laissée ouverte par défaut créerait des comptes sans aucun garde-fou en amont — exactement le genre de porte qu'on ouvre sans le vouloir en gardant un comportement par défaut hérité d'un outillage. Fermer par défaut, c'est décider que l'ouverture doit être un acte explicite, jamais un oubli.
Couche 5 — le DTO à liste blanche qui ne peut pas fuiter l'utilisateur
La dernière couche protège contre une erreur de programmation, pas contre un attaquant direct. Quand l'API renvoie le profil d'un compte, elle ne renvoie jamais l'entité utilisateur elle-même. Elle renvoie un objet de transfert dédié qui déclare, en liste blanche, exactement les champs autorisés à sortir — et rien d'autre.
Le raisonnement part d'un accident plausible. L'entité utilisateur contient des champs sensibles : l'empreinte du mot de passe, l'empreinte du jeton API, les secrets liés au second facteur. Sérialiser cette entité directement — par distraction, ou via un mécanisme intermédiaire qui la traverse — exposerait ces champs dans une réponse. La liste blanche rend cette fuite structurellement impossible : ce qui n'est pas explicitement autorisé ne sort pas, quelle que soit la manière dont on arrive à la sérialisation.
Une défense complémentaire double celle-ci : les accesseurs des champs les plus sensibles sont marqués comme à ignorer par le mécanisme de sérialisation. On a donc deux protections indépendantes contre le même risque — la liste blanche positive (« seuls ces champs sortent ») et le marquage négatif (« ces champs ne sortent jamais »). C'est la défense en profondeur à l'échelle d'un seul objet : même si l'une des deux est contournée par une évolution future du code, l'autre tient encore.
Ce que l'empilement produit
Aucune de ces cinq couches ne prétend suffire seule. Le token haché ne protège pas contre un compte volontairement mal configuré ; la 2FA ne protège pas une entité sérialisée par erreur ; la porte de pré-production ne dit rien de l'API. Prises ensemble, elles couvrent des risques de natures différentes — compromission de base, vol de session, exposition d'un environnement, ouverture par défaut involontaire, fuite par accident de sérialisation — sans qu'une seule défaillance ne fasse tout tomber.
La cohérence de l'ensemble tient à une posture commune, la même que celle d'une discipline d'ingénierie assumée : ne donner aucun accès par défaut, se défier de toute entrée, et révéler le moins possible sur le système. Chaque couche est une déclinaison de ces trois idées. Ce n'est pas la liste des mécanismes qui fait la sécurité — c'est le fait d'avoir, à chaque décision, préféré payer une friction maintenant plutôt qu'un incident plus tard.