Pourquoi Securiser une application React ?
Dans un monde où les menaces cybernétiques continuent à évoluer, la sécurité est devenue une priorité absolue pour toutes les applications, y compris celles développées en React. Un développement sécurisé n'est pas seulement une question d'éviter les pires scénarios, mais aussi de fournir une expérience utilisateur fiable et sécurisée.
Un cas concret de la nécessité de sécuriser une application React est lorsqu'un utilisateur entre des informations sensibles comme son mot de passe ou ses coordonnées bancaires. Si ces données ne sont pas correctement protégées, elles pourraient être volées et utilisées par des individus malveillants.
Prerequis
- Connaissances en JavaScript ES6+
- Familiarité avec React
- Connaissance de l'architecture du projet Create React App
- Installation de Node.js (v14 ou plus tard)
- Installation de npm ou yarn pour la gestion des dépendances
- Connaissance de bases de l'authentification et de la gestion des sessions
Concepts fondamentaux
1. Authentification et Autorisation
L'authentification est le processus par lequel une application vérifie l'identité d'un utilisateur. L'autorisation, quant à elle, détermine les actions que cet utilisateur peut effectuer dans l'application.
Schéma mental :
+-------------------+
| Authentification |
| |
| Identifiant |
| Mot de passe |
| |
+-------------------+
|
v
+-------------------+
| Autorisation |
| |
| Permissions |
| Roles |
| |
+-------------------+
Code fonctionnel :
// Auth.js
import React, { useState } from 'react';
const Auth = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
// Simuler une requête de connexion à un serveur
if (username === 'admin' && password === 'password') {
localStorage.setItem('user', JSON.stringify({ username }));
alert('Connexion réussie');
} else {
alert('Identifiants incorrects');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Se connecter</button>
</form>
);
};
export default Auth;
2. Gestion des Sessions
Une session utilisateur est une période durant laquelle un utilisateur est authentifié dans une application. Elle permet de garder les informations sur l'utilisateur entre les différentes pages.
Schéma mental :
+-------------------+
| Session |
| |
| Token |
| Expiration |
| |
+-------------------+
Code fonctionnel :
// App.js
import React, { useEffect } from 'react';
import Auth from './Auth';
const App = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<div>
{user ? (
<div>
<h1>Bienvenue, {user.username}</h1>
<button onClick={() => localStorage.removeItem('user')}>Se déconnecter</button>
</div>
) : (
<Auth setUser={setUser} />
)}
</div>
);
};
export default App;
3. Protection contre les Cross-Site Scripting (XSS)
L'XSS est une attaque où un attaquant injecte du code malveillant dans une page web qui est ensuite exécutée par d'autres utilisateurs.
Schéma mental :
+-------------------+
| XSS |
| |
| Injection |
| Affichage |
| Exécution |
| |
+-------------------+
Code fonctionnel :
// Commentaires.js
import React from 'react';
const Comment = ({ text }) => {
return <p>{text}</p>; // XSS vulnérable
};
const CommentsList = () => {
const comments = [
{ id: 1, text: '<script>alert("XSS")</script>' },
{ id: 2, text: 'Ceci est un commentaire' }
];
return (
<div>
{comments.map((comment) => (
<Comment key={comment.id} text={comment.text} />
))}
</div>
);
};
export default CommentsList;
Correction :
// Commentaires.js
import React from 'react';
const Comment = ({ text }) => {
return <p dangerouslySetInnerHTML=__html: text />; // Sécurisé contre XSS
};
const CommentsList = () => {
const comments = [
{ id: 1, text: '<script>alert("XSS")</script>' },
{ id: 2, text: 'Ceci est un commentaire' }
];
return (
<div>
{comments.map((comment) => (
<Comment key={comment.id} text={comment.text} />
))}
</div>
);
};
export default CommentsList;
Mise en pratique : projet fil rouge
Nous allons construire un mini-projet complet et réaliste : un gestionnaire de tâches simple. Ce projet comprendra une authentification, des tâches à ajouter et à supprimer, ainsi qu'une liste des tâches.
Étape 1 : Initialisation du projet
npx create-react-app todo-manager
cd todo-manager
npm start
Étape 2 : Authentification
Créez un composant Auth.js pour gérer l'authentification :
// src/Auth.js
import React, { useState } from 'react';
import axios from 'axios';
const Auth = ({ setUser }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('http://localhost:5000/login', { username, password });
localStorage.setItem('token', response.data.token);
setUser({ username });
} catch (error) {
alert('Identifiants incorrects');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Se connecter</button>
</form>
);
};
export default Auth;
Étape 3 : Gestion des tâches
Créez un composant TaskManager.js pour gérer les tâches :
// src/TaskManager.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const TaskManager = ({ user }) => {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
useEffect(() => {
if (user) {
fetchTasks();
}
}, [user]);
const fetchTasks = async () => {
try {
const response = await axios.get('http://localhost:5000/tasks', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
setTasks(response.data);
} catch (error) {
alert('Erreur lors de la récupération des tâches');
}
};
const addTask = async () => {
try {
await axios.post('http://localhost:5000/tasks', { text: newTask }, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
fetchTasks();
setNewTask('');
} catch (error) {
alert('Erreur lors de l ajout d une tâche');
}
};
const deleteTask = async (id) => {
try {
await axios.delete(`http://localhost:5000/tasks/${id}`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
fetchTasks();
} catch (error) {
alert('Erreur lors de la suppression d une tâche');
}
};
return (
<div>
<h1>Gestionnaire de Tâches</h1>
{user ? (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
placeholder="Nouvelle tâche"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button onClick={addTask}>Ajouter</button>
</form>
) : (
<p>Veuillez vous connecter pour utiliser le gestionnaire de tâches.</p>
)}
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.text}
<button onClick={() => deleteTask(task.id)}>Supprimer</button>
</li>
))}
</ul>
</div>
);
};
export default TaskManager;
Étape 4 : Affichage des composants
Modifiez le App.js pour afficher les composants Auth et TaskManager :
// src/App.js
import React, { useState } from 'react';
import Auth from './Auth';
import TaskManager from './TaskManager';
const App = () => {
const [user, setUser] = useState(null);
return (
<div>
{user ? (
<div>
<h1>Bienvenue, {user.username}</h1>
<TaskManager user={user} />
</div>
) : (
<Auth setUser={setUser} />
)}
</div>
);
};
export default App;
Erreurs frequentes et debugging
1. Authentification échoue
Code incorrect :
const handleSubmit = async (e) => {
e.preventDefault();
const response = await axios.post('http://localhost:5000/login', { username, password });
};
Correction :
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('http://localhost:5000/login', { username, password });
localStorage.setItem('token', response.data.token);
setUser({ username });
} catch (error) {
alert('Identifiants incorrects');
}
};
2. Ajout de tâche échoue
Code incorrect :
const addTask = async () => {
await axios.post('http://localhost:5000/tasks', { text: newTask });
fetchTasks();
setNewTask('');
};
Correction :
const addTask = async () => {
try {
await axios.post('http://localhost:5000/tasks', { text: newTask }, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
fetchTasks();
setNewTask('');
} catch (error) {
alert('Erreur lors de l ajout d une tâche');
}
};
3. Suppression de tâche échoue
Code incorrect :
const deleteTask = async (id) => {
await axios.delete(`http://localhost:5000/tasks/${id}`);
fetchTasks();
};
Correction :
const deleteTask = async (id) => {
try {
await axios.delete(`http://localhost:5000/tasks/${id}`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
fetchTasks();
} catch (error) {
alert('Erreur lors de la suppression d une tâche');
}
};
Pour aller plus loin
1. Utilisation de JWT pour l'authentification
JSON Web Tokens (JWT) sont un moyen sécurisé d'échanger des informations entre parties en tant que token.
Documentation :
2. Sécurité des données sensibles
Pour gérer les données sensibles comme les mots de passe, il est recommandé d'utiliser une hash function robuste et de saler le mot de passe avant son stockage.
Documentation :
3. Sécurité des formulaires
Les formulaires doivent être protégés contre les injections SQL et les attaques CSRF.
Documentation :
Défi pratique
Développez une fonction pour gérer la récupération de mot de passe (forget password) qui implique des vérifications d'e-mail et envoi d'un token de réinitialisation.
Documentation :
En suivant ce tutoriel, vous serez mieux équipé pour développer des applications React sécurisées. N'oubliez pas que la sécurité est un processus continu et que vous devrez toujours rester à jour sur les dernières menaces et meilleures pratiques.