Créer une API GraphQL sécurisée avec Node.js
Lawrence Eagles
29 mars 2022
0 minutes de lectureGraphQL intègre des mécanismes de sécurité dès le départ, avec la validation et la vérification des types. Toutefois, il ne répond pas entièrement aux enjeux de sécurité des API. Dans cet article, nous allons apprendre à sécuriser des API GraphQL en créant une application Node.js simple avec Fastify et GraphQL.
Selon sa documentation officielle, GraphQL est un langage de requête graphique pour les API et un environnement d’exécution permettant de traiter ces requêtes avec nos données. GraphQL décrit clairement les données de notre API et nous permet de créer des API rapides et flexibles, tout en laissant aux clients le contrôle total des données dont ils ont besoin. Comme REST, GraphQL fonctionne sur HTTP : il est donc indépendant des bases de données et compatible avec tout langage backend ou client.
Fastify est un framework Node.js basé sur des plugins, très efficace et extrêmement performant, adapté à la création de serveurs HTTP rapides. Inspiré de Hapi et Express, Fastify offre une alternative plus conviviale pour les développeurs et plus performante, avec une faible surcharge.
Fastify prend en charge GraphQL grâce au plugin Mercurius. Ce plugin est un adaptateur GraphQL configurable pour Fastify, que nous allons découvrir plus en détail dans les sections suivantes.
Commençons par les prérequis.
Prérequis
Voici les prérequis pour cet article :
Node.js version 12 ou ultérieure
Connaissances de base en JavaScript
Connaissances de base en GraphQL
Premiers pas
Pour commencer, nous devons créer un serveur Node.js de base.
Créez un projet de démarrage en créant un dossier de projet, puis exécutez le code ci-dessous dans l’interface de ligne de commande (CLI), depuis ce dossier. Cela initialisera notre application et installera les dépendances nécessaires.
Ce projet utilise une version spécifique de Mercurius. Pour l’installer, exécutez la commande suivante :
Ensuite, nous activons les modules ES6 pour utiliser le système de modules JavaScript standard plutôt que commonJS, en ajoutant "type": "module" à notre fichier package.json.
Ensuite, nous mettons à jour les scripts NPM avec une commande de démarrage du serveur Node.js. Pour cela, ouvrez le fichier package.json et modifiez la section des scripts comme suit :
Notez que l’extrait --es-module-specifier-resolution=node est nécessaire pour assurer l’interopérabilité entre les modules ES et les modules commonJS de Node.
Créez maintenant un répertoire src à la racine du projet. Dans le répertoire src, créez un dossier graphql contenant les fichiers schema.js et resolvers.js. Ajoutez le code suivant au fichier schema.js :
Ajoutez maintenant le code suivant au fichier resolvers.js :
Nous mettrons à jour le fichier schema.js et ajouterons du code au fichier resolvers.js dans la section suivante. Mais nous devons les créer dès maintenant avec du code passe-partout, car ils sont nécessaires au bon fonctionnement de notre serveur.
Dans le répertoire src, créez un fichier index.js contenant le code suivant :
Le code ci-dessus crée un serveur Fastify principal et enregistre le plugin Mercurius avec les options suivantes : schema, resolvers, graphiql et queryDepth.
Nous pouvons maintenant démarrer le serveur en exécutant npm run dev. Voici le résultat :
Nous pouvons constater que notre serveur fonctionne. Dans la section suivante, nous allons commencer à créer nos API de blog avec GraphQL.
Créer une API de blog sécurisée avec Fastify et GraphQL
Il existe plusieurs stratégies pour sécuriser une API, notamment :
Authentification et autorisation : l’authentification consiste à vérifier qu’un utilisateur est bien celui qu’il prétend être, tandis que l’autorisation concerne les permissions. L’authentification détermine si un utilisateur peut se connecter et permet ensuite de le reconnaître. L’autorisation définit les permissions attribuées à un utilisateur identifié et détermine s’il peut effectuer des opérations telles que créer, lire, mettre à jour ou supprimer.
Masquage des erreurs : ne pas divulguer les informations précises d’une erreur serveur afin d’éviter de révéler involontairement au client des détails susceptibles d’exposer les vulnérabilités du serveur.
Limitation de la profondeur des requêtes : définir une profondeur maximale pour les requêtes GraphQL. Les requêtes profondément imbriquées sont dangereuses, car elles mobilisent beaucoup de ressources et sont coûteuses à calculer. Elles peuvent donc faire tomber nos API.
Nettoyage et validation des entrées : utiliser des techniques standard de sécurité Web pour empêcher les utilisateurs d’envoyer des données malveillantes. Nous tirerons parti de la validation GraphQL intégrée à notre application.
Dans cet article, nous allons créer et sécuriser nos API à l’aide des stratégies ci-dessus, avec Mercurius et le plugin Mercurius Auth.
Pour nos besoins, le plugin Mercurius Auth offre deux fonctionnalités principales. Premièrement, il nous permet de définir des directives d’authentification personnalisées sur les champs de notre schéma. Ces directives sont des chaînes qui servent d’identifiants pour les champs protégés de notre schéma.
Il permet également d’appliquer des politiques d’authentification personnalisées à ces champs protégés lors d’une requête GraphQL.
Commençons par créer des données fictives. Dans le répertoire src, créez un dossier data contenant un fichier index.js avec le code ci-dessous :
Ensuite, configurons notre schema en remplaçant le code passe-partout du fichier schema.js par le code suivant :
Dans le code ci-dessus, nous avons créé notre schéma GraphQL et défini des directives d’authentification pour les champs user et users. Nous allons bientôt appliquer des politiques personnalisées à ces champs protégés.
Ajoutons maintenant nos résolveurs en remplaçant le code passe-partout du fichier resolvers.js par le code suivant :
Le code ci-dessus contient des résolveurs qui gèrent les requêtes user, users et login.
Enfin, nous devons ajouter une politique d’authentification personnalisée en enregistrant le plugin Mercurius Auth. Pour cela, dans le fichier index.js du répertoire src, ajoutons le code suivant sous le commentaire register auth policy, à la ligne 19 :
Dans notre politique personnalisée ci-dessus, la méthode authContext récupère le jeton utilisateur dans les en-têtes, tandis que la méthode applyPolicy contient les politiques personnalisées d’authentification et d’autorisation.
De plus, si l’authentification ou l’autorisation d’un utilisateur échoue, nous renvoyons une erreur accompagnée d’un message générique, par exemple « Une erreur s’est produite. Réessayez ! ». Ce message remplace un message d’erreur serveur détaillé, qui pourrait exposer d’éventuelles vulnérabilités du serveur.
Et voilà, notre travail est terminé. Nous allons tester nos API dans la section suivante.
Tester l’API
Commencez par démarrer le serveur en exécutant npm run dev depuis le répertoire racine. Ensuite, accédez à l’interface GraphQL Playground à l’adresse http://localhost:4500/playground.
Lorsque nous interrogeons une API protégée, comme users ou user, nous obtenons l’erreur illustrée ci-dessous :

Pour que notre requête aboutisse, nous devons donc nous authentifier. Connectons-nous pour obtenir un jeton.
Pour vous connecter, ouvrez un nouvel onglet dans Playground et exécutez la requête suivante :
Si la requête aboutit, un jeton est généré et renvoyé, comme illustré ci-dessous.

Notez que les données utilisateur utilisées pour la connexion figurent déjà dans le fichier index.js du répertoire data.
Si vous tentez de vous connecter avec des données utilisateur absentes de ce fichier (par exemple, le nom d’utilisateur « John1Doe »), une erreur s’affiche, comme illustré ci-dessous :

En transmettant notre jeton dans les en-têtes sous la forme x-user, nous pouvons désormais interroger nos API protégées, comme illustré ci-dessous. Veillez à copier le jeton reçu après la requête de connexion et à utiliser sa valeur pour x-user.
Requête users
Voici un exemple de requête users :
Ajoutez votre jeton comme valeur du paramètre d’en-tête HTTP x-user, comme illustré ci-dessous :
Voici le résultat obtenu :

Requête user
Voici un exemple de requête user :
Ajoutez votre jeton comme valeur du paramètre d’en-tête HTTP x-user, comme illustré ci-dessous :
Voici le résultat obtenu :

Conclusion
Dans cet article, nous avons découvert à quel point il est facile de sécuriser des API GraphQL en créant une application Node.js simple avec Fastify et GraphQL.
Comme nous l’avons vu, GraphQL intègre certains mécanismes de sécurité, tels que la validation et la vérification des types. Toutefois, la flexibilité et la puissance qui permettent aux utilisateurs de demander librement des données impliquent que la sécurité doit toujours être une priorité.
Cet article a également présenté plusieurs stratégies pour sécuriser les API GraphQL : l’authentification et l’autorisation, la limitation de la profondeur des requêtes, le masquage des erreurs, ainsi que le nettoyage et la validation des entrées.
Ces stratégies de sécurité sont très efficaces, mais nous pouvons renforcer encore la sécurité en mettant en œuvre d’autres méthodes, comme les délais d’expiration des requêtes et la limitation du débit, qui définissent la fréquence à laquelle un client peut interroger une API pendant une période donnée.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
Essayez gratuitement notre outil en ligne de vérification du code JavaScript pour découvrir comment le moteur Snyk Code analyse votre code afin d’identifier les problèmes de sécurité et de qualité.
