
Mise en place d'un système d'authentification simple (avec sessions)
Implementez un systeme d'authentification basique mais fonctionnel en Node.js et Express en utilisant les sessions cote serveur et les cookies.
Introduction a l'authentification basee sur les sessions
L'une des méthodes les plus classiques et éprouvées pour gérer l'authentification des utilisateurs dans les applications web est l'utilisation de sessions côté serveur. Contrairement aux approches stateless comme JWT (que nous verrons plus tard), l'authentification basée sur les sessions est stateful : le serveur conserve des informations sur l'état de connexion de chaque utilisateur entre les requêtes.
Le principe général est le suivant : lorsqu'un utilisateur se connecte avec succès (en fournissant des identifiants valides), le serveur crée un enregistrement unique pour cette session et stocke des informations pertinentes (comme l'ID de l'utilisateur). Le serveur envoie ensuite au navigateur du client un identifiant unique pour cette session (Session ID), généralement stocké dans un cookie. Lors des requêtes suivantes, le navigateur renvoie automatiquement ce cookie avec le Session ID. Le serveur utilise cet ID pour retrouver les informations de session stockées, vérifier que l'utilisateur est bien connecté et récupérer ses données associées.
Express.js facilite grandement la mise en place de ce mécanisme grâce à des middlewares dédiés, notamment le très populaire `express-session`.
Installation et configuration de `express-session`
Pour commencer, vous devez installer le package `express-session` comme dépendance de votre projet Node.js :
npm install express-sessionOu avec Yarn :
yarn add express-sessionUne fois installé, vous devez l'utiliser comme middleware dans votre application Express, généralement avant vos routes qui nécessitent une session. La configuration de ce middleware est cruciale pour la sécurité et le bon fonctionnement.
const express = require('express');
const session = require('express-session');
const app = express();
// Configuration du middleware de session
// !! Utiliser des variables d'environnement pour le secret en production !!
app.use(session({
secret: process.env.SESSION_SECRET || 'votre_secret_tres_tres_secret', // Clé pour signer le cookie de session
resave: false, // Ne pas sauvegarder la session si non modifiée
saveUninitialized: false, // Ne pas créer de session pour un utilisateur non authentifié
cookie: {
secure: process.env.NODE_ENV === 'production', // True si HTTPS, false sinon
httpOnly: true, // Empêche l'accès au cookie via JS côté client (sécurité XSS)
maxAge: 1000 * 60 * 60 * 24 // Durée de vie du cookie (ici, 24 heures en millisecondes)
// sameSite: 'lax' // Recommandé pour la protection CSRF (strict, lax, ou none)
}
// store: // Optionnel, pour la persistance des sessions en production (voir plus loin)
}));
// Reste de la configuration de l'application (routes, etc.)
// ...
app.listen(3000, () => console.log('Serveur avec sessions démarré sur le port 3000'));
Options de configuration importantes :
- `secret` : (Obligatoire) Une chaîne de caractères longue et aléatoire utilisée pour signer l'ID de session stocké dans le cookie. Cela empêche la falsification du cookie. Ne jamais la hardcoder, utilisez une variable d'environnement.
- `resave` : Si `false`, la session n'est pas réenregistrée dans le store si elle n'a pas été modifiée pendant la requête. Recommandé pour éviter les conditions de concurrence.
- `saveUninitialized` : Si `false`, une session n'est pas créée (et aucun cookie n'est envoyé) tant que l'objet session n'est pas modifié (par exemple, lors du login). Recommandé pour respecter la vie privée et éviter les cookies inutiles.
- `cookie` : Un objet pour configurer le cookie de session :
- `secure`: Mettez à `true` en production (si vous utilisez HTTPS) pour que le cookie ne soit envoyé que sur des connexions sécurisées.
- `httpOnly`: Essentiel pour la sécurité. Empêche le JavaScript côté client d'accéder au cookie, réduisant le risque d'attaques XSS (Cross-Site Scripting).
- `maxAge`: Durée de vie du cookie en millisecondes. Après ce délai, le cookie expire côté client.
- `sameSite`: Contrôle quand le cookie est envoyé lors de requêtes cross-site (protection CSRF). `'lax'` est souvent un bon compromis, `'strict'` est plus sûr mais peut casser certains flux de redirection.
- `store` : (Très important pour la production) Par défaut, `express-session` utilise un `MemoryStore` qui stocke les sessions dans la mémoire vive du serveur. Ce n'est absolument pas adapté à la production car : 1) il fuit la mémoire avec le temps, 2) les sessions sont perdues à chaque redémarrage, 3) il ne fonctionne pas si vous avez plusieurs instances de votre serveur (load balancing). Vous devez utiliser un store persistant comme `connect-redis`, `connect-mongo`, ou d'autres basés sur des bases de données SQL.
Le processus de connexion (Login)
Lorsque l'utilisateur soumet ses identifiants (généralement via un formulaire POST), votre route de connexion doit :
- Vérifier les identifiants fournis par rapport à votre base de données d'utilisateurs. Important : Ne stockez jamais les mots de passe en clair ! Stockez uniquement des hashs sécurisés (créés avec des bibliothèques comme `bcrypt`) et comparez le hash du mot de passe fourni avec le hash stocké.
- Si les identifiants sont valides, vous établissez la session en ajoutant des informations à l'objet `req.session`. Cet objet est automatiquement ajouté à l'objet `req` par le middleware `express-session`.
- Le simple fait de modifier `req.session` (par exemple, `req.session.userId = user.id;`) signale au middleware `express-session` qu'il doit sauvegarder la session dans le store et envoyer le cookie de session au client.
Exemple d'une route `/login` (simplifiée, sans hashage de mot de passe pour la clarté de l'exemple de session) :
// Assurez-vous d'avoir un middleware pour parser le corps des requêtes POST
app.use(express.urlencoded({ extended: false })); // Pour les données de formulaire
app.use(express.json()); // Pour le JSON
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// !! ETAPE 1: Vérification des identifiants (EXEMPLE SIMPLIFIE) !!
// En réalité, vous chercheriez l'utilisateur en BDD par username
// et utiliseriez bcrypt.compare(password, user.passwordHash)
const user = findUserInDatabase(username); // Fonction fictive
if (user && password === user.password) { // !! Comparaison simpliste !!
// !! ETAPE 2: Etablir la session !!
// Stocker des informations utiles dans la session
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
// La session est automatiquement sauvegardée et le cookie envoyé
console.log('Session établie pour:', user.username);
res.redirect('/dashboard'); // Rediriger vers une page protégée
} else {
res.status(401).send('Identifiants incorrects');
}
});
// Fonction fictive pour l'exemple
function findUserInDatabase(username) {
if (username === 'admin') return { id: 1, username: 'admin', password: 'password123', role: 'admin' };
return null;
}
Verifier l'etat de connexion (proteger des routes)
Une fois qu'un utilisateur est connecté, sa session existera sur le serveur et son navigateur enverra le cookie de session à chaque requête. Vous pouvez alors vérifier l'état de connexion dans vos routes ou, mieux, dans un middleware dédié.
Ce middleware vérifie simplement si les informations attendues (comme `userId`) existent dans `req.session`. Si oui, l'utilisateur est considéré comme authentifié et on appelle `next()` pour continuer. Sinon, on le redirige vers la page de connexion ou on renvoie une erreur 401/403.
// Middleware pour vérifier si l'utilisateur est authentifié
function isAuthenticated(req, res, next) {
if (req.session && req.session.userId) {
// L'utilisateur a une session valide
console.log('Utilisateur authentifié:', req.session.username);
return next(); // Continuer vers la route demandée
} else {
// L'utilisateur n'est pas connecté
console.log('Accès non authentifié refusé.');
res.redirect('/login'); // Rediriger vers la page de connexion
// Ou pour une API: res.status(401).send('Authentification requise');
}
}
// Appliquer le middleware aux routes nécessitant une authentification
app.get('/dashboard', isAuthenticated, (req, res) => {
// Seuls les utilisateurs connectés atteindront ce code
res.send(`Bienvenue sur votre tableau de bord, ${req.session.username} !`);
});
app.get('/profile', isAuthenticated, (req, res) => {
res.send(`Profil de ${req.session.username}. Role: ${req.session.role}`);
});
// Route de connexion (accessible publiquement)
app.get('/login', (req, res) => {
res.send('');
});
Le processus de deconnexion (Logout)
Pour déconnecter un utilisateur, il faut détruire sa session côté serveur. `express-session` fournit la méthode `req.session.destroy(callback)` pour cela. Cette méthode supprime la session du store. Il est aussi courant de demander au client de supprimer le cookie de session.
app.get('/logout', (req, res, next) => {
// Vérifier si une session existe avant de la détruire
if (req.session) {
req.session.destroy((err) => {
if (err) {
// Gérer une erreur rare lors de la destruction
console.error('Erreur lors de la destruction de la session:', err);
return next(err); // Passer à un gestionnaire d'erreur
}
// Optionnel : Supprimer le cookie côté client
// Le nom du cookie ('connect.sid' par défaut) peut devoir être ajusté
// res.clearCookie('connect.sid');
console.log('Session détruite.');
res.redirect('/'); // Rediriger vers la page d'accueil
});
} else {
// Pas de session à détruire, juste rediriger
res.redirect('/');
}
});
Conclusion : avantages et inconvenients des sessions
L'authentification basée sur les sessions avec `express-session` est une méthode robuste et relativement simple à mettre en place pour les applications web traditionnelles. Elle permet de stocker facilement l'état de l'utilisateur côté serveur.
Ses principaux inconvénients résident dans sa nature stateful :
- Scalabilité : Nécessite un store partagé (Redis, BDD) si vous avez plusieurs instances de serveur, ce qui ajoute de la complexité.
- Dépendance aux Cookies : Fonctionne moins bien avec les clients non-navigateurs (applications mobiles, autres services) qui ne gèrent pas les cookies aussi facilement.
- Performances : Chaque requête nécessite une recherche dans le store de session côté serveur.
Malgré ces points, c'est une excellente approche pour de nombreuses applications. Il est crucial de bien configurer le middleware (`secret`, options de cookie, `store` persistant en production) pour garantir la sécurité et la fiabilité du système.