4.2Authentification & sécurité (JWT, bcrypt, Passport, Helmet)
L'essentiel en 30 secondes
- Jamais de mot de passe en clair : on stocke le hachage bcrypt (à sens unique, salé, lent) et on compare à la connexion sans jamais déchiffrer.
- JWT = jeton signé auto-porteur : émis après connexion, renvoyé dans l'en-tête Authorization, vérifié par un middleware (401 non authentifié, 403 non autorisé).
- Réflexe de Salim : secret JWT en variable d'environnement (jamais en dur), Helmet activé, et « ne jamais faire confiance aux entrées ».
Mini-cas filé — Salim sécurise les comptes de l'API
L'API produits de Salim (4.1) doit maintenant gérer des vendeurs : inscription, connexion, routes réservées. Premier réflexe : hacher chaque mot de passe avec bcrypt avant stockage, puis émettre un JWT signé à la connexion réussie. Il écrit un middleware qui vérifie le jeton sur les routes protégées, distingue 401 (non connecté) et 403 (pas le droit), active Helmet et range son secret JWT dans une variable d'environnement. Quand un vendeur se connecte, son client renvoie le jeton à chaque requête — sans jamais exposer un mot de passe. Deuxième jalon du fil rouge : des comptes inviolables même si la base fuit.
Suite du cas 4.1 — Salim protège l'API qu'il vient de construire.
1. Les mots de passe : bcrypt, jamais en clair
Règle absolue : on ne stocke jamais un mot de passe en clair. On stocke son hachage — une transformation à sens unique, impossible à inverser. La bibliothèque standard est bcrypt : elle hache avec un sel aléatoire (contre les tables précalculées) et volontairement lentement (contre la force brute). À l'inscription on hache et on stocke le hachage ; à la connexion on compare (bcrypt.compare) sans jamais déchiffrer. Si la base est volée, les mots de passe restent protégés.
Ne codez jamais votre propre hachage « maison » : utilisez bcrypt ou un équivalent éprouvé. Et souvenez-vous que beaucoup d'utilisateurs réutilisent leurs mots de passe ailleurs : une fuite en clair les expose bien au-delà de votre application.
2. Maintenir la session : JWT
HTTP est sans état : après la connexion, il faut reconnaître l'utilisateur à chaque requête. Le JWT (JSON Web Token) est un jeton signé cryptographiquement, émis après connexion réussie ; le client le renvoie à chaque requête (en-tête Authorization: Bearer ...) et le serveur le vérifie — sans stocker de session, le JWT est « auto-porteur ». C'est l'approche la plus populaire pour les API et le mobile.
Un middleware protège les routes : jeton absent ou invalide → 401 ; jeton valide mais droits insuffisants → 403. Passport simplifie l'authentification (stratégies locales, OAuth Google/GitHub). Retenez la distinction : authentification (qui es-tu ? 401) vs autorisation (as-tu le droit ? 403).
3. La sécurité transversale
Au-delà de l'authentification : Helmet (middleware Express qui pose des en-têtes HTTP de sécurité), HTTPS en production, secrets en variables d'environnement (.env ignoré par Git, jamais dans le code), limitation de débit contre la force brute, validation des entrées (voir 4.4). Le principe qui sous-tend tout : ne jamais faire confiance aux entrées ni au client — tout ce qui vient de l'extérieur est potentiellement malveillant. La sécurité n'est pas une option ajoutée à la fin, c'est une préoccupation permanente, intégrée dès la conception.
🔎 Exemple concret — 🔎 Exemple concret — Inscription, connexion, route protégée
Le circuit complet de Salim, avec le secret lu depuis l'environnement :
POST /inscription → bcrypt.hash(motDePasse) → stocker le hachage
POST /connexion → bcrypt.compare → jwt.sign({ id }, SECRET)
GET /mon-compte → Authorization: Bearer ... → middleware vérifie
→ jeton absent/invalide : 401 · pas le droit : 403
Lecture : trois routes, trois responsabilités distinctes.
Leçon : authentification (qui ?) et autorisation (quel droit ?) sont deux filtres.
Vocabulaire de la section
- bcrypt
- Bibliothèque de hachage des mots de passe : à sens unique, avec sel aléatoire, volontairement lente contre la force brute.
Exemple simple : Salim hache à l'inscription et compare à la connexion, sans jamais déchiffrer.
Ce que ce n'est pas : un chiffrement réversible ou un hachage « maison ».
Réflexe : mot de passe en clair en base = faute grave, toujours hacher.
- JWT
- Jeton signé émis après connexion, renvoyé par le client à chaque requête et vérifié par le serveur ; sans état, idéal pour les API.
Exemple simple : Le vendeur de Salim reçoit son JWT à la connexion et le renvoie dans Authorization.
Ce que ce n'est pas : une session stockée côté serveur avec cookie.
Réflexe : vérifier la signature à chaque requête protégée.
- Authentification vs autorisation
- Authentification : QUI est l'utilisateur (401 si non). Autorisation : a-t-il le DROIT (403 si non).
Exemple simple : Salim renvoie 401 sans jeton, 403 pour un vendeur sur une route admin.
Ce que ce n'est pas : un seul contrôle flou qui mélange les deux.
Réflexe : choisir le bon code : 401 ou 403 ?
- Helmet
- Middleware Express qui définit des en-têtes HTTP de sécurité protégeant contre des failles web classiques.
Exemple simple : Salim l'active en une ligne : app.use(helmet()).
Ce que ce n'est pas : un système d'authentification complet à lui seul.
Réflexe : l'activer par défaut sur toute app Express.
- Variable d'environnement
- Configuration lue depuis l'environnement (fichier .env ignoré par Git), seul endroit acceptable pour les secrets.
Exemple simple : Le secret JWT de Salim vit dans process.env.JWT_SECRET, jamais dans le code.
Ce que ce n'est pas : une clé collée en dur dans le source et poussée sur Git.
Réflexe : .env ignoré par Git + exemple .env.example sans valeurs.
En pratique — En pratique — Sécuriser l'authentification
- Hachez les mots de passe avec bcrypt à l'inscription (jamais en clair) et comparez à la connexion (bcrypt.compare).
- Émettez un JWT signé après connexion réussie, avec un secret lu depuis process.env (jamais en dur).
- Écrivez un middleware qui VÉRIFIE le JWT sur les routes protégées (401 si absent/invalide) et attache l'utilisateur à req.user.
- Ajoutez Helmet, rangez tous les secrets en variables d'environnement (.env ignoré par Git) et distinguez 401 / 403.
Script prêt à dire — expliquer ses choix sécu en revue (30 s)
« C'est Salim : aucun mot de passe en clair, tout est haché avec bcrypt ; après connexion on émet un JWT signé dont le secret vit en variable d'environnement, jamais dans le code. Un middleware vérifie le jeton sur les routes protégées — 401 sans jeton valide, 403 sans le droit — et Helmet pose nos en-têtes de sécurité. »
À dire en revue de code ; montrez le .gitignore qui exclut .env.
Modèle copier-coller — auth bcrypt + JWT + Helmet (code testé)
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
app.use(helmet());
app.use(express.json());
const SECRET = process.env.JWT_SECRET; // jamais en dur !
if (!SECRET) throw new Error('JWT_SECRET manquant : définissez-le dans .env');
const vendeurs = []; // démo : en réel, base de données
// Inscription : on hache, on stocke le hachage
app.post('/inscription', async (req, res) => {
if (!req.body.email || !req.body.motDePasse) {
return res.status(400).json({ erreur: 'Email et mot de passe requis' });
}
const hachage = await bcrypt.hash(req.body.motDePasse, 10);
vendeurs.push({ email: req.body.email, hachage });
res.status(201).json({ message: 'Compte créé' });
});
// Connexion : on compare, on émet un JWT
app.post('/connexion', async (req, res) => {
const vendeur = vendeurs.find((v) => v.email === req.body.email);
if (!vendeur) return res.status(401).json({ erreur: 'Identifiants invalides' });
const ok = await bcrypt.compare(req.body.motDePasse, vendeur.hachage);
if (!ok) return res.status(401).json({ erreur: 'Identifiants invalides' });
const jeton = jwt.sign({ email: vendeur.email }, SECRET, { expiresIn: '1h' });
res.json({ jeton });
});
// Middleware : vérifie le JWT des routes protégées
function verifierJeton(req, res, next) {
const entete = req.headers.authorization || '';
const jeton = entete.replace('Bearer ', '');
if (!jeton) return res.status(401).json({ erreur: 'Jeton manquant' });
try {
req.utilisateur = jwt.verify(jeton, SECRET);
next();
} catch (err) {
return res.status(401).json({ erreur: 'Jeton invalide' });
}
}
app.get('/mon-compte', verifierJeton, (req, res) => {
res.json({ email: req.utilisateur.email });
});
app.listen(3000);
Petit projet perso
bcrypt + JWT + Helmet suffisent : ne complexifiez pas avec OAuth tant que le local couvre le besoin.
Connexion via Google/GitHub
Ajoutez Passport avec une stratégie OAuth : déléguez l'identité, gardez le JWT pour vos propres routes.
API exposée en production
Comme Salim : HTTPS obligatoire, JWT à durée courte, limitation de débit sur /connexion contre la force brute.
Équipe qui débute
Rituel non négociable : aucun secret dans le code ni dans Git — revue systématique avant chaque push.
.env ignoré par Git, jamais en dur dans le code), Helmet activé et HTTPS en production. Les exemples de ce guide utilisent des données fictives uniquement — aucun secret réel. En cas de doute sur des données personnelles, faites valider l'architecture par un référent technique. Contenu pédagogique, pas un audit de sécurité.✓ Bonnes pratiques
- Hacher avec bcrypt, comparer sans déchiffrer.
- Secret JWT en variable d'environnement, jamais en dur.
- Distinguer 401 (qui ?) et 403 (quel droit ?).
✗ Pièges fréquents
- Stocker des mots de passe en clair.
- Coller le secret JWT dans le code ou sur Git.
- Confondre authentification et autorisation.
Points clés à retenir
- JAMAIS de mot de passe en clair : HACHAGE BCRYPT (sens unique, sel, lent) ; comparer à la connexion. Jamais de hachage maison.
- JWT : jeton signé auto-porteur (Authorization), vérifié par middleware ; Passport simplifie (local, OAuth).
- AUTHENTIFICATION (401) vs AUTORISATION (403) — deux contrôles distincts.
- Helmet, HTTPS, SECRETS en variables d'environnement, rate limiting, validation. Principe : NE JAMAIS FAIRE CONFIANCE aux entrées.
Défi 48h — appliquez demain
Ajoutez inscription + connexion + 1 route protégée à votre API : bcrypt, JWT via process.env, Helmet — puis vérifiez le 401 sans jeton.
- Je peux expliquer bcrypt, JWT, 401 vs 403.
- Je sais placer un secret en variable d'environnement.
- J'ai 1 route protégée testée avec et sans jeton.
Questions fréquentes
Pourquoi ne jamais stocker les mots de passe en clair, et comment bcrypt protège-t-il ?
Si la base fuit (piratage, sauvegarde volée — cela arrive régulièrement), des mots de passe en clair exposent tous les comptes, et pire, les autres services où les utilisateurs réutilisent le même mot de passe. Le hachage est à sens unique : on stocke le hachage à l'inscription, on compare à la connexion, sans jamais pouvoir « retrouver » le mot de passe. bcrypt ajoute un sel aléatoire (contre les tables précalculées) et une lenteur volontaire (contre la force brute). Voler des hachages bcrypt ne permet pas de se connecter directement.
Par où commencer quand la sécurité semble intimidante ?
Par l'état d'esprit « ne jamais faire confiance aux entrées » — il prévient à lui seul une grande partie des failles — puis par les fondamentaux à fort impact : bcrypt pour les mots de passe, secrets en variables d'environnement (jamais dans Git), HTTPS en production, authentification et autorisation correctes (401/403), validation des entrées (4.4), Helmet. La sécurité s'intègre dès la conception, pas à la fin.
Où doit vivre le secret de signature JWT ?