GraphQL es una alternativa popular a la arquitectura API RESTful tradicional, que ofrece un lenguaje de manipulación y consulta de datos flexible y eficiente para API. Con su Con una adopción cada vez mayor, se vuelve cada vez más importante priorizar la seguridad de las API GraphQL para proteger las aplicaciones del acceso no autorizado y los datos potenciales. incumplimientos.

Un enfoque eficaz para proteger las API de GraphQL es implementar tokens web JSON (JWT). Los JWT proporcionan un método seguro y eficiente para otorgar acceso a recursos protegidos y realizar acciones autorizadas, garantizando una comunicación segura entre clientes y API.

Autenticación y autorización en las API GraphQL

A diferencia de API REST, las API GraphQL suelen tener un único punto final que permite a los clientes solicitar dinámicamente cantidades variables de datos en sus consultas. Si bien esta flexibilidad es su punto fuerte, también aumenta el riesgo de posibles ataques a la seguridad, como vulnerabilidades de control de acceso rotas.

instagram viewer

Para mitigar este riesgo, es importante implementar procesos sólidos de autenticación y autorización, incluida la definición adecuada de permisos de acceso. Al hacerlo, garantiza que solo los usuarios autorizados puedan acceder a los recursos protegidos y, en última instancia, reduce el riesgo de posibles violaciones de seguridad y pérdida de datos.

Puedes encontrar el código de este proyecto en su GitHub repositorio.

Configurar un servidor Apollo Express.js

Servidor Apolo es una implementación de servidor GraphQL ampliamente utilizada para las API de GraphQL. Puede usarlo para crear fácilmente esquemas GraphQL, definir solucionadores y administrar diferentes fuentes de datos para sus API.

Para configurar un servidor Express.js Apollo, cree y abra una carpeta de proyecto:

mkdir graphql-API-jwt
cd graphql-API-jwt

A continuación, ejecute este comando para inicializar un nuevo proyecto Node.js usando npm, el administrador de paquetes de Node:

npm init --yes

Ahora, instale estos paquetes.

npm install apollo-server graphql mongoose jsonwebtokens dotenv

Por último, cree un servidor.js archivo en el directorio raíz y configure su servidor con este código:

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);
});

El servidor GraphQL está configurado con el tipoDefs y solucionadores parámetros, especificando el esquema y las operaciones que la API puede manejar. El contexto La opción configura el objeto req en el contexto de cada solucionador, lo que permitirá al servidor acceder a detalles específicos de la solicitud, como los valores del encabezado.

Crear una base de datos MongoDB

Para establecer la conexión a la base de datos, primero crear una base de datos mongodb o configurar un clúster en MongoDB Atlas. Luego, copie la cadena URI de conexión de la base de datos proporcionada, cree una .env archivo e ingrese la cadena de conexión de la siguiente manera:

MONGO_URI=""

Definir el modelo de datos

Defina un modelo de datos usando Mongoose. Crear un nuevo modelos/usuario.js archivo e incluya el siguiente código:

const {model, Schema} = require('mongoose');

const userSchema = new Schema({
name: String,
password: String,
role: String
});

module.exports = model('user', userSchema);

Definir el esquema GraphQL

En una API GraphQL, el esquema define la estructura de los datos que se pueden consultar, además de delinear las operaciones disponibles (consultas y mutaciones) que puede realizar para interactuar con los datos a través del API.

Para definir un esquema, cree una nueva carpeta en el directorio raíz de su proyecto y asígnele un nombre graficoql. Dentro de esta carpeta, agregue dos archivos: tipoDefs.js y resolutores.js.

En el tipoDefs.js archivo, incluya el siguiente código:

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;

Crear solucionadores para la API GraphQL

Las funciones de resolución determinan cómo se recuperan los datos en respuesta a las consultas y mutaciones del cliente, así como otros campos definidos en el esquema. Cuando un cliente envía una consulta o mutación, el servidor GraphQL activa los solucionadores correspondientes para procesar y devolver los datos requeridos de varias fuentes, como bases de datos o API.

Para implementar la autenticación y autorización mediante tokens web JSON (JWT), defina solucionadores para las mutaciones de registro e inicio de sesión. Estos se encargarán de los procesos de registro y autenticación de usuarios. Luego, cree un solucionador de consultas de recuperación de datos al que solo podrán acceder usuarios autenticados y autorizados.

Pero primero, defina las funciones para generar y verificar los JWT. En el resolutores.js archivo, comience agregando las siguientes importaciones.

const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;

Asegúrese de agregar la clave secreta que utilizará para firmar tokens web JSON en el archivo .env.

SECRET_KEY = '';

Para generar un token de autenticación, incluya la siguiente función, que también especifica atributos únicos para el token JWT, por ejemplo, el tiempo de vencimiento. Además, puede incorporar otros atributos, como emitido en el momento, según los requisitos específicos de su aplicación.

functiongenerateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
 );

return token;
}

Ahora, implemente la lógica de verificación de tokens para validar los tokens JWT incluidos en solicitudes HTTP posteriores.

functionverifyToken(token) {
if (!token) {
thrownewError('Token not provided');
}

try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
thrownewError('Invalid token');
}
}

Esta función tomará un token como entrada, verificará su validez utilizando la clave secreta especificada y devolverá el token decodificado si es válido; de lo contrario, arrojará un error que indica un token no válido.

Definir los solucionadores de API

Para definir los solucionadores para la API GraphQL, debe delinear las operaciones específicas que administrará, en este caso, las operaciones de registro e inicio de sesión de usuario. Primero, crea un solucionadores objeto que contendrá las funciones de resolución, luego, defina las siguientes operaciones de mutación:

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');
}
}
},

El registro La mutación maneja el proceso de registro agregando los datos del nuevo usuario a la base de datos. Mientras que la acceso La mutación administra los inicios de sesión de los usuarios; si la autenticación es exitosa, generará un token JWT y devolverá un mensaje de éxito en la respuesta.

Ahora, incluya el solucionador de consultas para recuperar datos del usuario. Para garantizar que solo los usuarios autenticados y autorizados puedan acceder a esta consulta, incluya una lógica de autorización para restringir el acceso únicamente a los usuarios con una Administración role.

Básicamente, la consulta verificará primero la validez del token y luego la función del usuario. Si la verificación de autorización tiene éxito, la consulta de resolución procederá a buscar y devolver los datos de los usuarios de la base de datos.

 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');
}
},
},
};

Finalmente, inicie el servidor de desarrollo:

node server.js

¡Impresionante! Ahora, continúe y pruebe la funcionalidad de la API utilizando el entorno de pruebas de la API del servidor Apollo en su navegador. Por ejemplo, puedes utilizar el registro mutación para agregar nuevos datos de usuario en la base de datos, y luego, el acceso mutación para autenticar al usuario.

Por último, agregue el token JWT a la sección del encabezado de autorización y proceda a consultar la base de datos en busca de datos del usuario.

Proteger las API de GraphQL

La autenticación y la autorización son componentes cruciales para proteger las API de GraphQL. No obstante, es importante reconocer que es posible que por sí solos no sean suficientes para garantizar una seguridad integral. Debe implementar medidas de seguridad adicionales como validación de entradas y cifrado de datos confidenciales.

Al adoptar un enfoque de seguridad integral, puede proteger sus API contra diferentes ataques potenciales.