Securiser une application Express
Pourquoi Securiser une application Express ?
Securiser une application Express est essentiel pour prévenir les attaques et protéger les données sensibles des utilisateurs. Dans un monde où la sécurité est devenue un enjeu majeur, il est crucial d'implémenter des mesures efficaces pour assurer la confidentialité, l'intégrité et l'autenticité des transactions entre le client et le serveur.
Un cas d'utilisation concret est une plateforme de gestion de contenu qui gère des articles sensibles. Si cette application n'est pas sécurisée, elle pourrait être vulnérable à des attaques telles que les injections SQL, les cross-site scripting (XSS), ou les violations d'accès aux données.
Prerequis
- Connaissance du langage JavaScript et Node.js
- Familiarité avec le framework Express
- Compréhension de base des concepts de sécurité informatique (cryptage, authentification, autorisation)
- Installation de Node.js et npm (Node Package Manager)
Concepts fondamentaux
1. Middleware
Le middleware est une fonction qui accède à l'objet de la requête (req), à l'objet de la réponse (res) et à la chaîne de middleware suivante dans le cycle de traitement d'une requête/réponse. Elle peut exécuter des tâches telles que la modification de l'en-tête, la lecture du corps de la requête, ou même la finition de la réponse.
// Importer Express
const express = require('express');
const app = express();
// Middleware pour analyser le corps JSON de la requête
app.use(express.json());
// Middleware pour afficher les en-têtes de la requête
app.use((req, res, next) => {
console.log(`Headers: ${JSON.stringify(req.headers)}`);
next();
});
// Route simple pour tester
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
2. Authentification
L'authentification est le processus de vérification des identifiants utilisateurs pour confirmer leur identité. Dans une application Express, vous pouvez utiliser des bibliothèques comme passport.js pour faciliter cette tâche.
// Installer passport et les strategies nécessaires
npm install express passport passport-local
const express = require('express');
const app = express();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
app.use(express.json());
app.use(passport.initialize());
// Stratégie de base pour authentification locale
passport.use(new LocalStrategy(
(username, password, done) => {
// Vérifier l'identifiant et le mot de passe ici
if (username === 'admin' && password === 'password') {
return done(null, { id: 1, username });
} else {
return done(null, false, { message: 'Incorrect credentials.' });
}
}
));
// Série de fonctions pour sérialiser et désérialiser l'utilisateur
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
// Récupérer l'utilisateur à partir de la base de données
if (id === 1) {
return done(null, { id: 1, username: 'admin' });
} else {
return done(new Error('User not found'));
}
});
// Route pour se connecter
app.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}));
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
3. Autorisation
L'autorisation est le processus de vérification des permissions d'un utilisateur pour effectuer une certaine action. Vous pouvez utiliser des middleware personnalisés ou des bibliothèques comme can pour gérer les droits.
// Middleware d'autorisation basique
function checkPermission(permission) {
return (req, res, next) => {
if (req.user.permissions.includes(permission)) {
next();
} else {
res.status(403).send('Forbidden');
}
};
}
app.get('/admin', checkPermission('admin'), (req, res) => {
res.send('Welcome to the admin panel!');
});
Mise en pratique : projet fil rouge
Étape 1: Initialiser le projet
## Créer un nouveau dossier et initialiser npm
mkdir secure-app && cd secure-app
npm init -y
## Installer Express et autres dépendances
npm install express body-parser passport passport-local cors helmet
Étape 2: Configurer Express
Créez un fichier app.js :
// app.js
const express = require('express');
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(cors());
// Middleware pour analyser le corps JSON de la requête
app.use(express.json());
// Middleware pour afficher les en-têtes de la requête
app.use((req, res, next) => {
console.log(`Headers: ${JSON.stringify(req.headers)}`);
next();
});
// Initialiser Passport
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
if (id === 1) {
return done(null, { id: 1, username: 'admin' });
} else {
return done(new Error('User not found'));
}
});
// Stratégie de base pour authentification locale
passport.use(new LocalStrategy(
(username, password, done) => {
if (username === 'admin' && password === 'password') {
return done(null, { id: 1, username });
} else {
return done(null, false, { message: 'Incorrect credentials.' });
}
}
));
// Route pour se connecter
app.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}));
// Middleware d'autorisation basique
function checkPermission(permission) {
return (req, res, next) => {
if (req.user.permissions.includes(permission)) {
next();
} else {
res.status(403).send('Forbidden');
}
};
}
app.get('/admin', checkPermission('admin'), (req, res) => {
res.send('Welcome to the admin panel!');
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Étape 3: Créer des routes
Créez un fichier routes.js pour gérer les routes :
// routes.js
const express = require('express');
const router = express.Router();
router.get('/tasks', (req, res) => {
res.send('List of tasks');
});
router.post('/tasks', (req, res) => {
res.send('Create a new task');
});
module.exports = router;
Étape 4: Intégrer les routes
Modifiez app.js pour inclure les routes :
// app.js
const express = require('express');
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(cors());
// Middleware pour analyser le corps JSON de la requête
app.use(express.json());
// Middleware pour afficher les en-têtes de la requête
app.use((req, res, next) => {
console.log(`Headers: ${JSON.stringify(req.headers)}`);
next();
});
// Initialiser Passport
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
if (id === 1) {
return done(null, { id: 1, username: 'admin' });
} else {
return done(new Error('User not found'));
}
});
// Stratégie de base pour authentification locale
passport.use(new LocalStrategy(
(username, password, done) => {
if (username === 'admin' && password === 'password') {
return done(null, { id: 1, username });
} else {
return done(null, false, { message: 'Incorrect credentials.' });
}
}
));
// Route pour se connecter
app.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}));
// Middleware d'autorisation basique
function checkPermission(permission) {
return (req, res, next) => {
if (req.user.permissions.includes(permission)) {
next();
} else {
res.status(403).send('Forbidden');
}
};
}
app.get('/admin', checkPermission('admin'), (req, res) => {
res.send('Welcome to the admin panel!');
});
// Importer les routes
const routes = require('./routes');
app.use('/', routes);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Erreurs frequentes et debugging
1. Injection SQL
Code incorrect :
const db = require('mysql').createConnection({
host: 'localhost',
user: 'user',
password: 'password'
});
db.connect();
app.get('/users/:id', (req, res) => {
const id = req.params.id;
db.query(`SELECT * FROM users WHERE id = ${id}`, (err, results) => {
if (err) throw err;
res.send(results);
});
});
Code correct :
const db = require('mysql').createConnection({
host: 'localhost',
user: 'user',
password: 'password'
});
db.connect();
app.get('/users/:id', (req, res) => {
const id = req.params.id;
db.query('SELECT * FROM users WHERE id = ?', [id], (err, results) => {
if (err) throw err;
res.send(results);
});
});
2. XSS
Code incorrect :
app.get('/greet', (req, res) => {
const name = req.query.name;
res.send(`<h1>Hello, ${name}!</h1>`);
});
Code correct :
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.xssFilter());
app.get('/greet', (req, res) => {
const name = req.query.name;
res.send(`<h1>Hello, ${name}!</h1>`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
3. Violation d'accès aux données
Code incorrect :
const db = require('mysql').createConnection({
host: 'localhost',
user: 'user',
password: 'password'
});
db.connect();
app.get('/admin/users', (req, res) => {
const userId = req.user.id;
if (userId !== 1) {
return res.status(403).send('Forbidden');
}
db.query('SELECT * FROM users', (err, results) => {
if (err) throw err;
res.send(results);
});
});
Code correct :
const db = require('mysql').createConnection({
host: 'localhost',
user: 'user',
password: 'password'
});
db.connect();
app.get('/admin/users', checkPermission('admin'), (req, res) => {
db.query('SELECT * FROM users', (err, results) => {
if (err) throw err;
res.send(results);
});
});
Pour aller plus loin
- Utiliser des bibliothèques de sécurité avancées comme
helmetpour ajouter des protections supplémentaires contre les attaques courantes. - Implémenter la gestion des sessions sécurisées avec des tokens JWT (JSON Web Tokens).
- Intégrer une base de données cryptée et utiliser des hachages forts pour stocker les mots de passe.
Défi pratique
Défi : Implémentez un système de commentaires sur un blog en utilisant Express, Passport pour l'authentification, et MongoDB comme base de données. Assurez-vous d'inclure des mesures de sécurité appropriées pour protéger les données des utilisateurs.
Ce tutoriel vous a guide à travers la sécurisation d'une application Express, couvrant des concepts fondamentaux tels que le middleware, l'authentification et l'autorisation. En suivant ces étapes, vous serez en mesure de construire des applications plus robustes et sécurisées.