Qui parle au backend ? Une chaîne, pas une fonction
Les questions que pose toute authentification (identification, persistance, révocation, intégrité), la différence entre authentification et autorisation, les trois mécanismes que le projet combine — bcrypt, jeton signé, session en base — et pourquoi aucun ne suffit seul.
Précédemment. La partie « API » a construit un backend typé où chaque procédure reçoit un contexte, et où
protectedProcedureetisAdmindécident qui peut entrer. Ces gardes consultent toutes la même chose, que nous n’avons pas encore ouverte : la session.
Nous sortons de tRPC avec un backend structuré.
Nous savons fabriquer une procédure. Nous savons la protéger. Nous savons valider son entrée, gérer ses erreurs, contrôler sa sortie.
Et pourtant, à chaque fois que nous écrivons protectedProcedure, ou que nous lisons ctx.sessionClient.user.id dans un service, nous nous appuyons sur quelque chose que nous n'avons pas encore vraiment construit.
Une question silencieuse traverse tout le système :
qui parle au backend en ce moment ?
Cette question semble simple.
Mais elle contient en réalité plusieurs sous-questions, et chacune appelle une réponse différente.
- Qui es-tu ? (identification)
- Comment je te reconnais à la prochaine requête ? (persistance)
- Comment je sais que tu n'as pas changé d'avis ? (révocabilité)
- Comment je m'assure que tu es bien toi, et pas quelqu'un qui se fait passer pour toi ? (intégrité)
🧠 L'authentification, c'est l'ensemble des mécanismes qui répondent à ces questions.
Et c'est précisément parce qu'elle répond à plusieurs questions distinctes qu'elle ne peut jamais se résumer à un seul fichier.
Une chaîne, pas une fonction#
Dans beaucoup de projets juniors, l'auth est perçue comme un endpoint unique : POST /login. On envoie des identifiants, on reçoit un token, c'est terminé.
Cette vision est dangereuse.
Parce que ce qui se passe après le login est tout aussi important que le login lui-même : la session doit être stockée quelque part, transportée à chaque requête, vérifiée, révocable, expirable, nettoyable.
👉 L'authentification est une chaîne de responsabilités.
Et chaque maillon doit être correctement compris pour que l'ensemble tienne.
Deux questions, pas une#
Ces sous-questions se rangent en deux familles, qu’il faut distinguer dès maintenant parce que tout le reste de la partie les sépare.
- Qui est-ce ? C’est l’authentification : relier une requête à un compte, de façon fiable, à chaque fois.
- A-t-il le droit ? C’est l’autorisation : décider, pour ce compte, si l’action demandée est permise.
Le projet traite la première avec un mot de passe, un cookie et une session en base (chapitres 1 à 8) ; la seconde avec des groupes de membres (chapitre 9). La partie « API » a montré où ces deux réponses sont consommées — protectedProcedure, isAdmin. Il reste à voir d’où elles viennent.
🗺️ La feuille de route de cette partie#
Cette partie est dense. Pour ne pas nous y perdre, elle suit la vie complète d’une session, en trois mouvements répartis sur dix chapitres.
Mouvement 1 — La naissance d’une session (ce chapitre et les deux suivants).
Nous partons du clic du membre et nous suivons son adresse et son mot de passe jusqu’à ce qu’un cookie soit posé dans son navigateur : les mécanismes en jeu, le modèle de données et le contrat partagé, la validation, le service de connexion, la création de la session.
Mouvement 2 — La vie d’une session (chapitres 4 à 7).
Le cookie est posé. Que se passe-t-il à la requête suivante ? Comment le backend reconstruit-il l’identité à chaque appel, comment l’injecte-t-il dans le contexte tRPC ? Comment la session meurt-elle — déconnexion, expiration, changement de mot de passe — et comment le mot de passe oublié permet-il de reprendre la main ?
Mouvement 3 — Autour de la session (chapitres 8 à 10).
Le navigateur (comment React sait « qui je suis » dès le premier affichage), le proxy à l’entrée du site, puis l’autorisation : les groupes, les espaces, l’administrateur. Le dernier chapitre recompose l’ensemble, et passe en revue les pièges — dont plusieurs que le projet n’avait pas évités, et qui ont été corrigés pendant l’écriture de cette partie.
👉 À la fin, nous saurons exactement comment, à chaque requête entrant dans le système, le backend reconstruit l’identité du membre, et comment cette identité est ensuite mise à la disposition de toutes les procédures.
Un fil conducteur nous suivra du début à la fin : nous n’inventerons rien. Nous lirons du vrai code du projet, ligne par ligne, pour comprendre comment chaque pièce s’assemble avec les autres.
Un bon système d’authentification ne repose jamais sur un seul fichier.
C’est une chaîne de responsabilités entre validation, accès aux données, persistance, projection et injection de contexte.
Mouvement 1 — La naissance d’une session#
Dans ce premier mouvement, nous allons poser toutes les fondations de l’authentification et suivre le parcours complet d’une connexion réussie.
- Ce chapitre — Les deux problèmes que toute authentification cherche à résoudre, et les trois mécanismes que le projet combine pour les résoudre : bcrypt, un jeton signé, une session en base.
- Chapitre 2 — Le modèle
Sessiondans le schéma Prisma, ce qui y est volontairement absent, et le contratSessionClient, la frontière entre la base et le navigateur. - Chapitre 3 — Le parcours d’une connexion : la validation des identifiants, le service de connexion, et la création de la session avec son cookie.
À la fin de ce mouvement, un membre pourra s’être connecté. Son navigateur portera un cookie sécurisé, et le backend aura une trace persistante de sa session.
Les deux problèmes que toute authentification doit résoudre#
Avant d'ouvrir le moindre fichier, il faut bien sentir les deux problèmes distincts que toute authentification cherche à résoudre.
🧠 Problème 1 — L'identification#
"Qui es-tu ?"
C'est ce que fait le login.
L'utilisateur fournit deux preuves : son email (ce qu'il prétend être) et son mot de passe (ce qu'il sait). Le backend vérifie que ces deux preuves correspondent à un compte existant.
Si oui, l'utilisateur est identifié.
🧠 Problème 2 — La persistance#
"Comment je te reconnais à la prochaine requête ?"
C'est ici que le piège se referme sur les développeurs débutants.
Parce que HTTP est un protocole sans état. Chaque requête est indépendante. Le serveur n'a aucune mémoire entre deux requêtes du même utilisateur.
Si nous nous arrêtions au login, l'utilisateur devrait fournir email + mot de passe à chaque clic. Inacceptable.
👉 Il faut donc fabriquer un mécanisme de persistance qui survit entre les requêtes.
L'analogie du club#
Imaginons un club privé.
À l'entrée, le portier nous demande notre carte d'adhérent (login). Il vérifie notre identité, puis nous tamponne la main avec une encre invisible (création de session).
Tant que le tampon est lisible sous la lampe UV, nous pouvons entrer et sortir librement. Le portier passe simplement notre main sous la lampe à chaque visite (vérification de session).
Si le tampon s'efface (expiration), il faut repasser par la case identification.
Et si le club décide que nous ne sommes plus le bienvenu, il peut nous bannir — notre tampon ne sera plus reconnu, même s'il est encore visible (révocation).
👉 Login + session + révocation : c'est exactement le triptyque que nous allons construire.
Identification et persistance sont deux problèmes distincts.
L'un répond à "qui es-tu ?". L'autre répond à "comment je te reconnais ensuite ?".
Confondre les deux mène à des systèmes fragiles.
👉 Où nous en sommes : nous avons posé le cadre conceptuel. Deux problèmes, deux réponses distinctes. Il est temps maintenant de découvrir les mécanismes concrets qui vont les résoudre dans notre projet.
L’arsenal du projet : trois mécanismes complémentaires#
Pour résoudre ces deux problèmes, le projet utilise trois mécanismes qui jouent chacun un rôle précis.
Les trois mécanismes de l’auth AKFC
Mécanisme | Rôle | Où il vit |
|---|---|---|
bcrypt | Transformer le mot de passe en empreinte, pour qu’il ne soit jamais stocké en clair | lib/auth/password.ts |
JWT (jeton signé) | Signer un jeton transporté dans un cookie, qui prouve l’authenticité de la session | createSessionJWT, verifyJwt |
Session en base | Garder la session côté serveur pour pouvoir la révoquer | Table Session |
Showing entries1to3entries
Cette combinaison n'est pas accidentelle. C'est un choix architectural précis.
🔭 Pour aller plus loin — pourquoi l'hybride JWT + Session DB ?#
Il existe trois grandes stratégies pour gérer la persistance d'une session :
Stratégie 1 — JWT pur (stateless)
Toute l'information vit dans le JWT lui-même. Le backend ne stocke rien. À chaque requête, il vérifie la signature et fait confiance au contenu.
- Avantage : très scalable, pas de lookup DB
- Inconvénient majeur : impossible de révoquer un JWT avant son expiration. Si l'utilisateur clique "logout", ou si l'admin veut bannir un compte, le JWT reste valide jusqu'à sa date d'expiration
Stratégie 2 — Session DB pure
Le serveur génère un identifiant aléatoire, le stocke en base, et le renvoie au client (souvent dans un cookie). À chaque requête, il fait un lookup DB.
- Avantage : révocation immédiate (suppression de la ligne en base)
- Inconvénient : aucune information dans le cookie lui-même — un attaquant peut tenter du brute-force sur les IDs
Stratégie 3 — Hybride JWT + Session DB (le choix du projet)
On combine les deux. Le JWT contient seulement un sessionId, signé. La session est stockée en base.
- Avantage 1 : signature cryptographique → impossible de forger un JWT valide sans connaître
JWT_SECRET - Avantage 2 : présence en base → révocable instantanément en supprimant la ligne
Session - Avantage 3 : on peut faire expirer côté serveur indépendamment du JWT
C'est le compromis le plus robuste pour un backend qui veut à la fois la solidité cryptographique et le contrôle serveur.
bcrypt — pourquoi l’empreinte compte#
bcrypt sert à transformer le mot de passe en une empreinte avant de l’enregistrer.
Le principe : nous ne stockons jamais un mot de passe en clair. Nous en stockons une empreinte, conçue pour être impossible à inverser.
À la connexion, nous recalculons l’empreinte du mot de passe fourni, avec les paramètres de l’empreinte enregistrée, et comparons. Les deux correspondent → mot de passe correct.
bcrypt ajoute deux protections :
- un sel aléatoire intégré à chaque empreinte : deux membres qui ont le même mot de passe ont deux empreintes différentes, et une table d’empreintes précalculées ne sert à rien ;
- un coût réglable qui rend volontairement le calcul lent : avec un coût de 12, bcrypt enchaîne 2¹² tours, soit environ 250 ms par essai. Pour un membre, c’est imperceptible ; pour un attaquant qui essaie des millions de mots de passe sur une empreinte volée, c’est rédhibitoire.
Dans le projet, tout ce qui touche aux empreintes passe par un seul fichier :
import bcrypt from "bcryptjs";/** * Mots de passe : SOURCE UNIQUE du hachage et de la vérification. * * Une empreinte bcrypt contient son sel et son coût : deux membres qui ont le même mot de passe * ont deux empreintes différentes, et la vérification n'a besoin que de l'empreinte. *//** Coût bcrypt : 2^12 tours, environ 250 ms par calcul. Lent exprès, pour freiner les essais. */export const PASSWORD_HASH_COST = 12;/** L'empreinte à enregistrer pour un mot de passe en clair. */export function hashPassword(plainPassword: string): Promise<string> { return bcrypt.hash(plainPassword, PASSWORD_HASH_COST);}/** Le mot de passe en clair correspond-il à l'empreinte enregistrée ? */export function verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean> { return bcrypt.compare(plainPassword, hashedPassword);}/** * Empreinte d'un mot de passe aléatoire que personne ne connaît, au même coût que les vraies. * Sert quand l'adresse saisie n'a pas de compte : on vérifie quand même, contre cette empreinte, * pour que la réponse prenne le même temps. Sinon, la rapidité de la réponse trahirait qu'une * adresse est inconnue. */export const UNKNOWN_ACCOUNT_HASH = "$2b$12$0mKIykkePGG1TWy0XVc0bewZAYuDz/KD4osvKCMECmza3MKtG4YV2";Ce fichier est récent. Jusqu’ici, le coût 12 était recopié dans quatre fichiers (bcrypt.hash(…, 12)), et la connexion appelait bcrypt.compare directement alors qu’une fonction verifyPassword existait déjà. Le jour où il aurait fallu relever le coût, un oubli aurait suffi pour qu’une partie des mots de passe reste moins protégée. La dernière constante du fichier, UNKNOWN_ACCOUNT_HASH, prendra tout son sens au chapitre 3.
👉 Où nous en sommes : nous avons identifié les trois outils du coffre. Pour vraiment comprendre comment ils coopèrent, nous devons maintenant descendre dans la base de données et regarder la structure qui porte tout cela.
À suivre#
Les outils sont sur la table. Pour comprendre comment ils coopèrent, il faut descendre dans la base, regarder la structure qui porte la session, et décider de ce qui en sortira vers le navigateur.
Chapitre suivant : le modèle et le contrat. Le modèle Session, pourquoi aucun droit n’y est recopié, la chaîne qui mène au groupe Administrateurs, et le contrat SessionClient.