Accueil Technologie

Comment sécuriser une API REST Node.js à l’aide de jetons Web JSON

Lorsque vous créez une application, il est essentiel de protéger les données sensibles contre tout accès non autorisé. De nombreuses applications web, mobiles et cloud modernes utilisent les API REST comme principal moyen de communication. Il est donc essentiel de concevoir et de développer des API dorsales en mettant l’accent sur la sécurité.


Une approche efficace pour sécuriser une API REST consiste à utiliser des jetons Web JSON (JWT). Ces jetons offrent un mécanisme robuste d’authentification et d’autorisation des utilisateurs, ce qui permet de protéger les ressources protégées contre l’accès d’acteurs malveillants.

Que sont les jetons Web JSON ?

Le jeton Web JSON (JWT) est une norme de sécurité largement utilisée. Il s’agit d’une méthode concise et autonome de transmission sécurisée de données entre une application client et un système dorsal.

Une API REST peut utiliser des JWT pour identifier et authentifier en toute sécurité les utilisateurs lorsqu’ils font des demandes HTTP pour accéder à des ressources protégées.

Exemple d'un jeton JWT encodé à gauche et de la version décodée du jeton montrant les composants individuels à droite.

Un jeton Web JSON se compose de trois parties distinctes : l’en-tête, la charge utile et la signature. Chaque partie est codée et concaténée à l’aide d’un point (« . »).

L’en-tête décrit l’algorithme cryptographique utilisé pour signer le jeton, tandis que la charge utile contient des données sur l’utilisateur et d’éventuelles métadonnées supplémentaires.

Enfin, la signature, calculée à l’aide de l’en-tête, de la charge utile et de la clé secrète, garantit l’intégrité et l’authenticité du jeton.

Les bases des JWT étant posées, construisons une API REST Node.js et mettons en œuvre les JWT.

La mise en place d’une application Express.js et d’une base de données MongoDB

Vous découvrirez ici comment construire une API REST d’authentification simple qui gère à la fois les fonctionnalités d’enregistrement et de connexion. Une fois que le processus de connexion authentifie un utilisateur, celui-ci doit pouvoir effectuer des requêtes HTTP vers une route API protégée.

Vous pouvez trouver le code du projet dans ce dépôt GitHub.

Pour commencer, créez un serveur web Express et installez ces paquets :

 npm install cors dotenv bycrpt mongoose cookie-parser crypto jsonwebtoken mongodb 

Ensuite, créez une base de données MongoDB ou configurez un cluster MongoDB sur le cloud. Copiez ensuite la chaîne de connexion à la base de données, créez un fichier .env dans le répertoire racine, et collez la chaîne de connexion :

 CONNECTION_STRING="connection string" 

Configurer la connexion à la base de données

Créer un nouveau utils/db.js dans le répertoire racine de votre dossier de projet. Dans ce fichier, ajoutez le code suivant pour établir la connexion à la base de données à l’aide de Mongoose.

 const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.CONNECTION_STRING);
    console.log("Connected to MongoDB!");
  } catch (error) {
    console.error("Error connecting to MongoDB:", error);
  }
};

module.exports = connectDB;

Définir le modèle de données

Définissez un schéma de données utilisateur simple à l’aide de Mongoose. Dans le répertoire racine, créez un nouveau fichier model/user.model.js et ajoutez le code suivant.

 const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  password: {
    type: String,
    required: true,
    unique: true,
  },
});

const User = mongoose.model("User", userSchema);
module.exports = User;

Définir les contrôleurs pour les routes API

Les fonctions du contrôleur gèrent l’enregistrement et la connexion ; elles constituent une partie importante de cet exemple de programme. Dans le répertoire racine, créez un fichier controllers/userControllers.js et ajoutez le code suivant :

  1. Définir le contrôleur d’enregistrement des utilisateurs.
     const User = require('../models/user.model');
    const bcrypt = require('bcrypt');
    const { generateToken } = require('../middleware/auth');

    exports.registerUser = async (req, res) => {
      const { username, password } = req.body;

      try {
        const hash = await bcrypt.hash(password, 10);
        await User.create({ username, password: hash });
        res.status(201).send({ message: 'User registered successfully' });
      } catch (error) {
        console.log(error);
        res.status(500).send({ message: 'An error occurred!! ' });
      }
    };

    Cet extrait de code hache le mot de passe fourni à l’aide de bcrypt et crée ensuite un nouvel enregistrement d’utilisateur dans la base de données, en stockant le nom d’utilisateur et le mot de passe haché. Si l’enregistrement est réussi, il envoie une réponse avec un message de succès.

  2. Définir un contrôleur de connexion pour gérer le processus de connexion de l’utilisateur :
     exports.loginUser = async (req, res) => {
      const { username, password } = req.body;

      try {
        const user = await User.findOne({ username });
      
        if (!user) {
          return res.status(404).send({ message: 'User not found' });
        }

        const passwordMatch = await bcrypt.compare(password, user.password);

        if (!passwordMatch) {
          return res.status(401).send({ message: 'Invalid login credentials' });
        }

        const payload = { userId: user._id };
        const token = generateToken(payload);
        res.cookie('token', token, { httpOnly: true });
        res.status(200).json({ message: 'Login successful'});
      } catch (error) {
        console.log(error);
        res.status(500).send({ message: 'An error occurred while logging in' });
      }
    };

    Lorsqu’un utilisateur envoie une demande au /login ils doivent transmettre leurs informations d’authentification dans le corps de la requête. Le code vérifie ensuite ces informations d’identification et génère un jeton Web JSON. Le jeton est stocké de manière sécurisée dans un cookie avec l’identifiant httpOnly est fixé à true. Cela empêche le JavaScript côté client d’accéder au jeton, ce qui permet de se protéger contre d’éventuelles attaques de type cross-site scripting (XSS).

  3. Enfin, définissez un itinéraire protégé :
     exports.getUsers = async (req, res) => {
      try {
        const users = await User.find({});
        res.json(users);
      } catch (error) {
        console.log(error);
        res.status(500).send({ message: 'An error occurred!!' });
      }
    };

    En stockant le JWT dans un cookie, les demandes d’API ultérieures de l’utilisateur authentifié incluront automatiquement le jeton, ce qui permettra au serveur de valider et d’autoriser les demandes.

Créer un intergiciel d’authentification

Maintenant que vous avez défini un contrôleur de connexion qui génère un jeton JWT en cas d’authentification réussie, définissez les fonctions d’authentification de l’intergiciel qui généreront et vérifieront le jeton JWT.

Dans le répertoire racine, créez un nouveau dossier, middleware. A l’intérieur de ce dossier, ajoutez deux fichiers : auth.js et config.js.

Ajouter ce code à config.js:

 const crypto = require('crypto');

module.exports = {
  secretKey: crypto.randomBytes(32).toString('hex')
};

Ce code génère une nouvelle clé secrète aléatoire à chaque exécution. Vous pouvez ensuite utiliser cette clé secrète pour signer et vérifier l’authenticité des JWT. Une fois l’utilisateur authentifié, générez et signez un JWT avec la clé secrète. Le serveur utilisera alors la clé pour vérifier que le JWT est valide.

Ajouter le code suivant dans auth.js qui définit les fonctions middleware qui génèrent et vérifient les JWT.

 const jwt = require('jsonwebtoken');
const { secretKey } = require('./config');

const generateToken = (payload) => {
  const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
  return token ;
};

const verifyToken = (req, res, next) => {
  const token = req.cookies.token;

  if (!token) {
    return res.status(401).json({ message: 'No token provided' });
  }

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      return res.status(401).json({ message: 'Invalid token' });
    }

    req.userId = decoded.userId;
    next();
  });
};

module.exports = { generateToken, verifyToken };

L’outil generateToken génère un JWT en signant une charge utile à l’aide d’une clé secrète et en fixant un délai d’expiration pendant que la fonction verifyToken La fonction verifyToken sert d’intergiciel pour vérifier l’authenticité et la validité d’un jeton fourni.

Définir les routes API

Créer un nouveau routes/userRoutes.js dans le répertoire racine et ajoutez le code suivant.

 const express = require('express');
const router = express.Router();
const userControllers = require('../controllers/userControllers');
const { verifyToken } = require('../middleware/auth');
router.post('/api/register', userControllers.registerUser);
router.post('/api/login', userControllers.loginUser);
router.get('/api/users', verifyToken, userControllers.getUsers);
module.exports = router;

Mise à jour du point d’entrée de votre serveur

Mettez à jour votre server.js avec le code suivant.

 const express = require('express');
const cors = require('cors');
const app = express();
const port = 5000;
require('dotenv').config();
const connectDB = require('./utils/db');
const cookieParser = require('cookie-parser');

connectDB();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(cookieParser());
const userRoutes = require('./routes/userRoutes');
app.use("https://www.makeuseof.com/", userRoutes);

app.listen(port, () => {
  console.log(`Server is listening at http://localhost:${port}`);
});

Pour tester l’API REST, démarrez le serveur de développement et envoyez des requêtes API aux points d’extrémité définis :

 node server.js 

Sécuriser les API REST de Node.js

La sécurisation des API REST Node.js ne se limite pas à l’utilisation des JWT. Bien qu’ils jouent un rôle crucial dans l’authentification et l’autorisation, il est essentiel d’adopter une approche holistique de la sécurité pour protéger vos systèmes dorsaux. Outre les JWT, vous devriez également envisager de mettre en œuvre HTTPS pour crypter les communications, la validation et l’assainissement des entrées, et bien d’autres choses encore.

En combinant plusieurs mesures de sécurité, vous pouvez établir un cadre de sécurité solide pour vos API REST Node.js et minimiser le risque d’accès non autorisé, de violation des données et d’autres menaces de sécurité.

Leave your vote

0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires

Log In

Forgot password?

Don't have an account? Register

Forgot password?

Enter your account data and we will send you a link to reset your password.

Your password reset link appears to be invalid or expired.

Log in

Privacy Policy

Add to Collection

No Collections

Here you'll find all collections you've created before.

0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x

Newsletter

inscrivez vous pour recevoir nos actualités

Actualités, astuces, bons plans et cadeaux !