GraphQL é uma alternativa popular à arquitetura tradicional de API RESTful, oferecendo uma linguagem flexível e eficiente de consulta e manipulação de dados para APIs. Com seu adoção crescente, torna-se cada vez mais importante priorizar a segurança das APIs GraphQL para proteger os aplicativos contra acesso não autorizado e dados potenciais violações.

Uma abordagem eficaz para proteger APIs GraphQL é implementar JSON Web Tokens (JWTs). Os JWTs fornecem um método seguro e eficiente para conceder acesso a recursos protegidos e executar ações autorizadas, garantindo uma comunicação segura entre clientes e APIs.

Autenticação e autorização em APIs GraphQL

Diferente API REST, as APIs GraphQL normalmente têm um único endpoint que permite aos clientes solicitar dinamicamente quantidades variadas de dados em suas consultas. Embora essa flexibilidade seja o seu ponto forte, ela também aumenta o risco de possíveis ataques à segurança, como vulnerabilidades de controle de acesso quebradas.

Para mitigar este risco, é importante implementar processos robustos de autenticação e autorização, incluindo a definição adequada de permissões de acesso. Ao fazer isso, você garante que apenas usuários autorizados possam acessar recursos protegidos e, em última análise, reduz o risco de possíveis violações de segurança e perda de dados.

instagram viewer

Você pode encontrar o código deste projeto em seu GitHub repositório.

Configurar um servidor Express.js Apollo

Servidor Apolo é uma implementação de servidor GraphQL amplamente usada para APIs GraphQL. Você pode usá-lo para criar facilmente esquemas GraphQL, definir resolvedores e gerenciar diferentes fontes de dados para suas APIs.

Para configurar um Express.js Apollo Server, crie e abra uma pasta de projeto:

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

A seguir, execute este comando para inicializar um novo projeto Node.js usando npm, o gerenciador de pacotes do Node:

npm init --yes

Agora, instale esses pacotes.

npm install apollo-server graphql mongoose jsonwebtokens dotenv

Por último, crie um servidor.js arquivo no diretório raiz e configure seu servidor com 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);
});

O servidor GraphQL é configurado com o typeDefs e resolvedores parâmetros, especificando o esquema e as operações que a API pode manipular. O contexto A opção configura o objeto req para o contexto de cada resolvedor, o que permitirá ao servidor acessar detalhes específicos da solicitação, como valores de cabeçalho.

Crie um banco de dados MongoDB

Para estabelecer a conexão com o banco de dados, primeiro criar um banco de dados MongoDB ou configurar um cluster no MongoDB Atlas. Em seguida, copie a string URI de conexão do banco de dados fornecida e crie um .env arquivo e insira a string de conexão da seguinte forma:

MONGO_URI=""

Defina o modelo de dados

Defina um modelo de dados usando Mongoose. Crie um novo modelos/user.js arquivo e inclua o seguinte código:

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

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

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

Defina o esquema GraphQL

Em uma API GraphQL, o esquema define a estrutura dos dados que podem ser consultados, bem como delineia as operações disponíveis (consultas e mutações) que você pode realizar para interagir com os dados por meio do API.

Para definir um esquema, crie uma nova pasta no diretório raiz do seu projeto e nomeie-a gráficoql. Dentro desta pasta, adicione dois arquivos: typeDefs.js e resolvedores.js.

No typeDefs.js arquivo, inclua o seguinte 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;

Crie resolvedores para a API GraphQL

As funções do resolvedor determinam como os dados são recuperados em resposta às consultas e mutações do cliente, bem como outros campos definidos no esquema. Quando um cliente envia uma consulta ou mutação, o servidor GraphQL aciona os resolvedores correspondentes para processar e retornar os dados necessários de várias fontes, como bancos de dados ou APIs.

Para implementar autenticação e autorização usando JSON Web Tokens (JWTs), defina resolvedores para as mutações de registro e login. Eles cuidarão dos processos de registro e autenticação do usuário. Em seguida, crie um resolvedor de consulta de busca de dados que só estará acessível a usuários autenticados e autorizados.

Mas primeiro defina as funções para gerar e verificar os JWTs. No resolvedores.js arquivo, comece adicionando as seguintes importações.

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

Certifique-se de adicionar a chave secreta que você usará para assinar tokens da web JSON no arquivo .env.

SECRET_KEY = '';

Para gerar um token de autenticação, inclua a função a seguir, que também especifica atributos exclusivos para o token JWT, por exemplo, o tempo de expiração. Além disso, você pode incorporar outros atributos, como os emitidos no momento, com base nos requisitos específicos da sua aplicação.

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

return token;
}

Agora, implemente a lógica de verificação de token para validar os tokens JWT incluídos nas solicitações HTTP subsequentes.

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 função receberá um token como entrada, verificará sua validade usando a chave secreta especificada e retornará o token decodificado se for válido, caso contrário, gerará um erro indicando um token inválido.

Defina os resolvedores de API

Para definir os resolvedores da API GraphQL, é necessário delinear as operações específicas que ela irá gerenciar, neste caso, as operações de registro e login do usuário. Primeiro, crie um resolvedores objeto que conterá as funções do resolvedor, então, defina as seguintes operações de mutação:

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

O registro A mutação lida com o processo de registro adicionando os novos dados do usuário ao banco de dados. Enquanto o Conecte-se A mutação gerencia logins de usuários – na autenticação bem-sucedida, ela gerará um token JWT, bem como retornará uma mensagem de sucesso na resposta.

Agora inclua o resolvedor de consulta para recuperar dados do usuário. Para garantir que esta consulta será acessível apenas a usuários autenticados e autorizados, inclua uma lógica de autorização para restringir o acesso apenas a usuários com um Administrador papel.

Essencialmente, a consulta verificará primeiro a validade do token e, em seguida, a função do usuário. Se a verificação de autorização for bem-sucedida, a consulta do resolvedor irá buscar e retornar os dados dos usuários do banco de dados.

 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 o servidor de desenvolvimento:

node server.js

Incrível! Agora vá em frente e teste a funcionalidade da API usando a sandbox da API do Apollo Server em seu navegador. Por exemplo, você pode usar o registro mutação para adicionar novos dados do usuário no banco de dados e, em seguida, o Conecte-se mutação para autenticar o usuário.

Por último, adicione o token JWT à seção do cabeçalho de autorização e prossiga para consultar o banco de dados em busca de dados do usuário.

Protegendo APIs GraphQL

Autenticação e autorização são componentes cruciais para proteger APIs GraphQL. No entanto, é importante reconhecer que por si só podem não ser suficientes para garantir uma segurança abrangente. Você deve implementar medidas de segurança adicionais, como validação de entrada e criptografia de dados confidenciais.

Ao adotar uma abordagem de segurança abrangente, você pode proteger suas APIs contra diversos ataques potenciais.