đ ActivitĂ© Pratique : Renforcement de la SĂ©curitĂ© dâun Formulaire de Connexion
Dans cette activité, nous allons prendre un formulaire de connexion de base et y appliquer des bonnes pratiques pour renforcer sa sécurité. L'objectif est de prévenir les attaques courantes comme le XSS, le CSRF, et le brute force, tout en protégeant les données utilisateur.
Objectif de l'activité
Nous prenons un formulaire de connexion de base et y appliquons les bonnes pratiques pour le durcir : prévention des attaques XSS, CSRF et brute force, tout en protégeant les données utilisateur.
đ PrĂ©paration du Formulaire de Connexion de Base
Code HTML de départ :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion</title>
</head>
<body>
<form id="loginForm" action="/login" method="POST">
<label for="username">Nom d'utilisateur :</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Se connecter</button>
</form>
</body>
</html>Code Node.js de départ :
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Vérification des identifiants (exemple fictif)
if (username === 'admin' && password === 'password123') {
res.send('Connexion réussie');
} else {
res.status(401).send('Nom dâutilisateur ou mot de passe incorrect');
}
});
app.listen(3000, () => {
console.log('Serveur en cours dâexĂ©cution sur http://localhost:3000');
});Ce formulaire fonctionne, mais il est vulnérable à plusieurs attaques. Nous allons maintenant y ajouter des mesures de sécurité.
đ Mesures de SĂ©curisation
đ a. Protection contre les Attaques XSS
Vulnérabilité XSS :
Les attaquants peuvent injecter des scripts malveillants dans les champs de formulaire, qui s'exécuteront sur le navigateur de la victime.
Solution :
- Valider et échapper toutes les entrées utilisateur cÎté serveur.
- Ajouter une Content Security Policy (CSP) pour restreindre les sources de scripts.
Mise en Ćuvre dans Node.js :
const sanitizeHtml = require('sanitize-html');
app.post('/login', (req, res) => {
const username = sanitizeHtml(req.body.username);
const { password } = req.body; // â ïž ne jamais "sanitizer" un mot de passe : il est comparĂ© tel quel (via hash), pas affichĂ© dans une page HTML
if (username === 'admin' && password === 'password123') {
res.send('Connexion réussie');
} else {
res.status(401).send('Nom dâutilisateur ou mot de passe incorrect');
}
});Ne jamais sanitizer un mot de passe
sanitizeHtml retire ou modifie des caractÚres (<, >, &...) qui peuvent pourtant faire partie d'un mot de passe légitime. L'appliquer au mot de passe le corromprait avant la comparaison. La sanitization HTML protÚge le contenu affiché dans une page ; un mot de passe n'est jamais réinjecté tel quel dans du HTML, il est comparé à un hash stocké (voir bcrypt/argon2 dans les fondamentaux du chapitre).
Ajout dâune CSP dans les headers HTTP :
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self';"
);
next();
});đ b. Protection contre les Attaques CSRF
Vulnérabilité CSRF :
Un attaquant peut forcer un utilisateur authentifiĂ© Ă envoyer une requĂȘte malveillante (par exemple, en cliquant sur un lien malicieux).
Solution :
- Utiliser des jetons CSRF pour valider que chaque requĂȘte POST provient de lâutilisateur lĂ©gitime.
csurf est déprécié
Le paquet historique csurf est archivĂ© et non maintenu depuis 2022 (vulnĂ©rabilitĂ©s non corrigĂ©es). Utilisez une alternative maintenue comme csrf-csrf, qui reprend le mĂȘme principe de jeton en double-soumission (cookie + valeur transmise dans le formulaire).
Installation et utilisation de csrf-csrf :
npm install csrf-csrf cookie-parserAjout dans lâapplication :
const cookieParser = require('cookie-parser');
const { doubleCsrf } = require('csrf-csrf');
const { generateToken, doubleCsrfProtection } = doubleCsrf({
getSecret: () => 'votre_secret_csrf',
cookieName: 'csrf-token',
});
app.use(cookieParser());
app.get('/login', (req, res) => {
const csrfToken = generateToken(req, res);
res.send(`
<form id="loginForm" action="/login" method="POST">
<input type="hidden" name="_csrf" value="${csrfToken}">
<label for="username">Nom d'utilisateur :</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Se connecter</button>
</form>
`);
});
app.post('/login', doubleCsrfProtection, (req, res) => {
const { username, password } = req.body;
if (username === 'admin' && password === 'password123') {
res.send('Connexion réussie');
} else {
res.status(401).send('Nom dâutilisateur ou mot de passe incorrect');
}
});đ c. Limitation des Attaques par Force Brute
Vulnérabilité :
Les attaquants peuvent essayer plusieurs combinaisons de mots de passe jusquâĂ en trouver un correct.
Solution :
- Limiter le nombre de tentatives de connexion.
- Implémenter un délai ou un blocage temporaire aprÚs plusieurs échecs.
Installation et utilisation de express-rate-limit :
npm install express-rate-limitAjout dans lâapplication :
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limite Ă 5 tentatives
message: 'Trop de tentatives de connexion. Réessayez plus tard.',
});
app.post('/login', loginLimiter, (req, res) => {
const { username, password } = req.body;
if (username === 'admin' && password === 'password123') {
res.send('Connexion réussie');
} else {
res.status(401).send('Nom dâutilisateur ou mot de passe incorrect');
}
});đȘ d. Utilisation de Cookies SĂ©curisĂ©s
Vulnérabilité :
Les cookies peuvent ĂȘtre volĂ©s via des attaques XSS ou envoyĂ©s sur des connexions non sĂ©curisĂ©es.
Solution :
- Ajouter les options
HttpOnly,Secure, etSameSiteaux cookies de session.
Exemple :
const session = require('express-session');
app.use(session({
secret: 'votre_secret',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false, // Passez Ă true si vous utilisez HTTPS
sameSite: 'strict',
},
}));đ RĂ©sultat Final
Avec ces améliorations, notre formulaire de connexion est désormais protégé contre :
- XSS : Validation des entrĂ©es et ajout dâune CSP.
- CSRF : Implémentation de jetons CSRF.
- Attaques par force brute : Limitation des tentatives de connexion.
- Vol de session : Cookies sécurisés.
app.js complet (toutes les mesures combinées) :
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const sanitizeHtml = require('sanitize-html');
const { doubleCsrf } = require('csrf-csrf');
const rateLimit = require('express-rate-limit');
const session = require('express-session');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
secret: 'votre_secret',
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, secure: false, sameSite: 'strict' },
}));
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self';"
);
next();
});
const { generateToken, doubleCsrfProtection } = doubleCsrf({
getSecret: () => 'votre_secret_csrf',
cookieName: 'csrf-token',
});
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5,
message: 'Trop de tentatives de connexion. Réessayez plus tard.',
});
app.get('/login', (req, res) => {
const csrfToken = generateToken(req, res);
res.send(`
<form id="loginForm" action="/login" method="POST">
<input type="hidden" name="_csrf" value="${csrfToken}">
<label for="username">Nom d'utilisateur :</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Se connecter</button>
</form>
`);
});
app.post('/login', loginLimiter, doubleCsrfProtection, (req, res) => {
const username = sanitizeHtml(req.body.username);
const { password } = req.body;
if (username === 'admin' && password === 'password123') {
res.send('Connexion réussie');
} else {
res.status(401).send('Nom dâutilisateur ou mot de passe incorrect');
}
});
app.listen(3000, () => {
console.log('Serveur en cours dâexĂ©cution sur http://localhost:3000');
});Tester lâapplication :
- Lancez le serveur :
node app.js - AccĂ©dez Ă
http://localhost:3000/loginpour tester le formulaire sĂ©curisĂ©. - Effectuez des tests avec des attaques simulĂ©es (par exemple, en injectant des scripts ou en envoyant plusieurs requĂȘtes incorrectes).
Ces bonnes pratiques renforcent significativement la sĂ©curitĂ© des systĂšmes dâauthentification tout en maintenant une expĂ©rience utilisateur fluide.
Vérification rapide
Quiz du chapitre
Authentification
Terminez le quiz du chapitre pour le marquer comme complété.
đ Bonnes Pratiques de SĂ©curisation dans les SystĂšmes d'Authentification
Pour garantir la sĂ©curitĂ© des systĂšmes dâauthentification, il est essentiel dâadopter des pratiques reconnues pour protĂ©ger les donnĂ©es sensibles, prĂ©venir les attaques, et renforcer la confiance des utilisateurs.
đĄïž Technologies AvancĂ©es pour lâAuthentification
Les technologies modernes offrent des moyens avancés pour sécuriser l'accÚs aux systÚmes tout en améliorant l'expérience utilisateur. Parmi ces technologies, WebAuthn et l'intégration de l'authentification multi-facteurs (MFA) sont devenues des standards.