GraphQL est une alternative populaire à l'architecture API RESTful traditionnelle, offrant un langage de requête et de manipulation de données flexible et efficace pour les API. Avec son adoption croissante, il devient de plus en plus important de donner la priorité à la sécurité des API GraphQL pour protéger les applications contre les accès non autorisés et les données potentielles violations.
Une approche efficace pour sécuriser les API GraphQL consiste à implémenter des jetons Web JSON (JWT). Les JWT fournissent une méthode sécurisée et efficace pour accorder l'accès aux ressources protégées et effectuer des actions autorisées, garantissant ainsi une communication sécurisée entre les clients et les API.
Authentification et autorisation dans les API GraphQL
Contrairement à API REST, les API GraphQL ont généralement un seul point de terminaison qui permet aux clients de demander dynamiquement différentes quantités de données dans leurs requêtes. Bien que cette flexibilité soit sa force, elle augmente également le risque d'attaques potentielles de sécurité telles que des vulnérabilités de contrôle d'accès brisées.
Pour atténuer ce risque, il est important de mettre en œuvre des processus d'authentification et d'autorisation robustes, notamment en définissant correctement les autorisations d'accès. Ce faisant, vous garantissez que seuls les utilisateurs autorisés peuvent accéder aux ressources protégées et, en fin de compte, réduisez le risque de failles de sécurité potentielles et de perte de données.
Vous pouvez trouver le code de ce projet dans son GitHub dépôt.
Configurer un serveur Apollo Express.js
Serveur Apollo est une implémentation de serveur GraphQL largement utilisée pour les API GraphQL. Vous pouvez l'utiliser pour créer facilement des schémas GraphQL, définir des résolveurs et gérer différentes sources de données pour vos API.
Pour configurer un serveur Apollo Express.js, créez et ouvrez un dossier de projet :
mkdir graphql-API-jwt
cd graphql-API-jwt
Ensuite, exécutez cette commande pour initialiser un nouveau projet Node.js à l'aide de npm, le gestionnaire de packages Node:
npm init --yes
Maintenant, installez ces packages.
npm install apollo-server graphql mongoose jsonwebtokens dotenv
Enfin, créez un serveur.js fichier dans le répertoire racine et configurez votre serveur avec ce code :
const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req }),
});const MONGO_URI = process.env.MONGO_URI;
mongoose
.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to DB");
return server.listen({ port: 5000 });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.log(err.message);
});
Le serveur GraphQL est configuré avec le typeDefs et résolveurs paramètres, spécifiant le schéma et les opérations que l'API peut gérer. Le contexte L'option configure l'objet req selon le contexte de chaque résolveur, ce qui permettra au serveur d'accéder aux détails spécifiques à la demande tels que les valeurs d'en-tête.
Créer une base de données MongoDB
Pour établir la connexion à la base de données, commencez par créer une base de données MongoDB ou configurer un cluster sur MongoDB Atlas. Ensuite, copiez la chaîne URI de connexion à la base de données fournie, créez un .env fichier et entrez la chaîne de connexion comme suit :
MONGO_URI=""
Définir le modèle de données
Définissez un modèle de données à l'aide de Mongoose. Créer un nouveau modèles/user.js fichier et incluez le code suivant :
const {model, Schema} = require('mongoose');
const userSchema = new Schema({
name: String,
password: String,
role: String
});
module.exports = model('user', userSchema);
Définir le schéma GraphQL
Dans une API GraphQL, le schéma définit la structure des données qui peuvent être interrogées, ainsi que les grandes lignes les opérations disponibles (requêtes et mutations) que vous pouvez effectuer pour interagir avec les données via le API.
Pour définir un schéma, créez un nouveau dossier dans le répertoire racine de votre projet et nommez-le graphql. Dans ce dossier, ajoutez deux fichiers: typeDefs.js et résolveurs.js.
Dans le typeDefs.js fichier, incluez le code suivant :
const { gql } = require("apollo-server");
const typeDefs = gql`
type User {
id: ID!
name: String!
password: String!
role: String!
}
input UserInput {
name: String!
password: String!
role: String!
}
type TokenResult {
message: String
token: String
}
type Query {
users: [User]
}
type Mutation {
register(userInput: UserInput): User
login(name: String!, password: String!, role: String!): TokenResult
}
`;
module.exports = typeDefs;
Créer des résolveurs pour l'API GraphQL
Les fonctions du résolveur déterminent la manière dont les données sont récupérées en réponse aux requêtes et mutations des clients, ainsi qu'à d'autres champs définis dans le schéma. Lorsqu'un client envoie une requête ou une mutation, le serveur GraphQL déclenche les résolveurs correspondants pour traiter et renvoyer les données requises à partir de diverses sources, telles que des bases de données ou des API.
Pour implémenter l'authentification et l'autorisation à l'aide de jetons Web JSON (JWT), définissez des résolveurs pour les mutations de registre et de connexion. Ceux-ci géreront les processus d’enregistrement et d’authentification des utilisateurs. Ensuite, créez un résolveur de requêtes de récupération de données qui ne sera accessible qu'aux utilisateurs authentifiés et autorisés.
Mais d’abord, définissez les fonctions pour générer et vérifier les JWT. Dans le résolveurs.js fichier, commencez par ajouter les importations suivantes.
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
Assurez-vous d'ajouter la clé secrète que vous utiliserez pour signer les jetons Web JSON au fichier .env.
SECRET_KEY = '' ;
Pour générer un jeton d'authentification, incluez la fonction suivante, qui spécifie également des attributs uniques pour le jeton JWT, par exemple le délai d'expiration. De plus, vous pouvez intégrer d'autres attributs tels que ceux émis à un moment donné en fonction des exigences spécifiques de votre application.
functiongenerateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
);
return token;
}
Maintenant, implémentez la logique de vérification des jetons pour valider les jetons JWT inclus dans les requêtes HTTP suivantes.
functionverifyToken(token) {
if (!token) {
thrownewError('Token not provided');
}
try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
thrownewError('Invalid token');
}
}
Cette fonction prendra un jeton en entrée, vérifiera sa validité à l'aide de la clé secrète spécifiée et renverra le jeton décodé s'il est valide, sinon générera une erreur indiquant un jeton invalide.
Définir les résolveurs d'API
Pour définir les résolveurs pour l'API GraphQL, vous devez décrire les opérations spécifiques qu'elle gérera, dans ce cas, les opérations d'enregistrement et de connexion des utilisateurs. Tout d'abord, créez un résolveurs objet qui contiendra les fonctions de résolveur, puis définissez les opérations de mutation suivantes :
const resolvers = {
Mutation: {
register: async (_, { userInput: { name, password, role } }) => {
if (!name || !password || !role) {
thrownewError('Name password, and role required');
}const newUser = new User({
name: name,
password: password,
role: role,
});try {
const response = await newUser.save();return {
id: response._id,
...response._doc,
};
} catch (error) {
console.error(error);
thrownewError('Failed to create user');
}
},
login: async (_, { name, password }) => {
try {
const user = await User.findOne({ name: name });if (!user) {
thrownewError('User not found');
}if (password !== user.password) {
thrownewError('Incorrect password');
}const token = generateToken(user);
if (!token) {
thrownewError('Failed to generate token');
}
return {
message: 'Login successful',
token: token,
};
} catch (error) {
console.error(error);
thrownewError('Login failed');
}
}
},
Le registre mutation gère le processus d'enregistrement en ajoutant les nouvelles données utilisateur à la base de données. Tandis que le se connecter La mutation gère les connexions des utilisateurs: en cas d'authentification réussie, elle générera un jeton JWT et renverra un message de réussite dans la réponse.
Maintenant, incluez le résolveur de requêtes pour récupérer les données utilisateur. Pour garantir que cette requête ne sera accessible qu'aux utilisateurs authentifiés et autorisés, incluez une logique d'autorisation pour restreindre l'accès aux seuls utilisateurs disposant d'un Administrateur rôle.
Essentiellement, la requête vérifiera d’abord la validité du jeton, puis le rôle de l’utilisateur. Si la vérification d'autorisation réussit, la requête du résolveur procédera à la récupération et au renvoi des données des utilisateurs à partir de la base de données.
Query: {
users: async (parent, args, context) => {
try {
const token = context.req.headers.authorization || '';
const decodedToken = verifyToken(token);if (decodedToken.role !== 'Admin') {
thrownew ('Unauthorized. Only Admins can access this data.');
}
const users = await User.find({}, { name: 1, _id: 1, role:1 });
return users;
} catch (error) {
console.error(error);
thrownewError('Failed to fetch users');
}
},
},
};
Enfin, démarrez le serveur de développement :
node server.js
Génial! Maintenant, allez-y et testez la fonctionnalité de l'API à l'aide du bac à sable de l'API Apollo Server dans votre navigateur. Par exemple, vous pouvez utiliser le registre mutation pour ajouter de nouvelles données utilisateur dans la base de données, puis, le se connecter mutation pour authentifier l’utilisateur.
Enfin, ajoutez le jeton JWT à la section d'en-tête d'autorisation et procédez à l'interrogation de la base de données pour les données utilisateur.
Sécuriser les API GraphQL
L'authentification et l'autorisation sont des composants cruciaux pour sécuriser les API GraphQL. Néanmoins, il est important de reconnaître qu’ils ne suffiront peut-être pas à eux seuls à garantir une sécurité globale. Vous devez mettre en œuvre des mesures de sécurité supplémentaires telles que la validation des entrées et le cryptage des données sensibles.
En adoptant une approche de sécurité globale, vous pouvez protéger vos API contre différentes attaques potentielles.