Jetons chiffrés à la volée
Les jetons d'authentification ne sont jamais stockés en clair. Seul un digest SHA-256 est persisté ; le jeton lui-même n'est remis au client qu'une seule fois à l'émission.
Comment nous émettons, stockons et révoquons les jetons qui vous gardent connecté.
Chaque connexion génère une AuthSession révocable liée à un seul client. Les jetons sont hachés avant d'atteindre le disque, tournés à chaque changement de mot de passe, et vérifiés côté fournisseur pour les connexions sociales. Vous pouvez voir chaque session active et les révoquer individuellement — il n'y a aucun jeton permanent caché.
Les jetons d'authentification ne sont jamais stockés en clair. Seul un digest SHA-256 est persisté ; le jeton lui-même n'est remis au client qu'une seule fois à l'émission.
Chaque session (mobile, web, montre, Twoody) correspond à un enregistrement distinct, révocable à tout moment — par le support ou par l'utilisateur lors d'un changement de mot de passe.
Toutes les sessions actives sont invalidées lorsque le mot de passe change. Aucun jeton n'a de durée de vie illimitée.
Sign in with Apple et Google Sign-In sont supportés. Les identifiants JWT sont vérifiés contre les clés publiques (JWKS) de chaque fournisseur, l'audience est vérifiée contre notre allow-list, le nonce est vérifié quand fourni, et tout jeton déjà consommé est rejeté.
Sur iOS et Android, les jetons sont stockés dans le trousseau (Keychain / Keystore) du système, protégé par le code ou la biométrie de l'appareil. Sur macOS et Apple Watch, même pratique, avec un mode « haute sécurité » optionnel déclenchant FaceID/TouchID à chaque requête sensible.
Les mots de passe sont hachés avec bcrypt à un work factor qui prend plusieurs centaines de millisecondes par tentative. Même une fuite complète de la base exigerait une puissance de calcul énorme pour brute-forcer un seul mot de passe fort.
L'appairage d'appareil utilise un code à 6 chiffres qui expire après 10 minutes et ne peut être validé que quelques fois avant d'être invalidé. Un tiers ne peut pas observer une brève installation et détourner votre appareil plus tard.
L'écran « Sessions » de votre profil liste chaque bearer actif (appareil, dernière activité, type de client). La révocation est immédiate — la requête suivante portant ce jeton est rejetée côté serveur. Se déconnecter ne révoque que la session courante.
Sur POST /session, nous cherchons l'utilisateur par email (NULL-safe contre les comptes soft-deleted), comparons le hash bcrypt en temps constant, et en cas de succès créons une nouvelle ligne AuthSession liée à l'empreinte du client (user agent, type de client). La ligne stocke un digest SHA-256 du bearer ; le jeton en clair est renvoyé une seule fois, dans la réponse JSON. Le client l'écrit dans le keystore de l'OS. Nous ne revoyons ni ne stockons plus jamais le clair.
À chaque requête suivante, le client envoie le bearer dans X-User-Token. Nous le hachons, cherchons le digest, vérifions que la session n'est ni révoquée ni expirée, et bumpons last_seen_at (coalescé à une fois par minute pour garder les écritures bon marché). Si l'une de ces vérifications échoue, la requête reçoit un 401 — aucun nombre de retries ne change ça.
Chaque AuthSession mappe un seul client. Une nouvelle connexion depuis un nouvel appareil n'invalide pas les sessions existantes sur d'autres appareils — vous êtes toujours libre de voir la liste sur /sessions et de révoquer chacune individuellement. Côté serveur, la révocation est immédiate : la requête suivante avec ce jeton est rejetée.
Certains événements déclenchent une révocation globale : un changement de mot de passe invalide toutes les sessions actives (l'hypothèse étant que vous tournez le mot de passe en réponse à une compromission suspectée), et une éviction admin peut révoquer tous les bearers d'un utilisateur en un seul appel.
Quand vous vous connectez avec Apple ou Google, votre appareil négocie un JWT ID token avec le fournisseur. Nous récupérons les JWKS publiques du fournisseur (cachées 10 minutes), vérifions la signature du token, l'issuer, l'audience (contre une allow-list ENV) et le nonce si fourni. L'email du token est ensuite matché contre un compte existant non-supprimé, ou un nouveau est créé.
Le bearer que nous renvoyons est identique à celui de POST /session — le code client est unifié. Sous le capot, la même ligne AuthSession est créée, avec un tag provider supplémentaire pour la traçabilité.