Implementando autenticação de usuário em Express.js usando JWTs

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 sua crescente adoção, torna-se cada vez mais importante priorizar a segurança das APIs GraphQL para proteger os aplicativos contra acesso não autorizado e possíveis violações de dados.

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 comunicação segura entre clientes e APIs.

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

Ao contrário das APIs 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.

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

Em seguida, execute este comando para inicializar um novo projeto Node.js usando npm, o gerenciador de pacotes Node:

 npm init --yes 

Agora, instale esses pacotes.

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

Por último, crie um arquivo server.js 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 os parâmetros typeDefs e resolvedores, especificando o esquema e as operações que a API pode manipular. A opção context 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.

  As bolhas musicais controlam o Google Music com um botão flutuante onipresente

Crie um banco de dados MongoDB

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

 MONGO_URI="<mongo_connection_uri>"

Defina o modelo de dados

Defina um modelo de dados usando Mongoose. Crie um novo arquivo models/user.js 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 descreve as operações disponíveis (consultas e mutações) que você pode realizar para interagir com os dados por meio da API.

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

No arquivo typeDefs.js, 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.

  Abas do Safari desaparecendo no iPhone/iPad: 7 correções para experimentar!

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 arquivo resolvers.js, 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 = '<my_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.

 function generateToken(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.

 function verifyToken(token) {
  if (!token) {
    throw new Error('Token not provided');
  }

  try {
    const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
    return decoded;
  } catch (err) {
    throw new Error('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 objeto resolvedor que conterá as funções do resolvedor e, em seguida, defina as seguintes operações de mutação:

 const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('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);
        throw new Error('Failed to create user');
      }
    },
    login: async (_, { name, password }) => {
      try {
        const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('User not found');
       }

        if (password !== user.password) {
          throw new Error('Incorrect password');
        }

        const token = generateToken(user);

        if (!token) {
          throw new Error('Failed to generate token');
        }

        return {
          message: 'Login successful',
          token: token,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Login failed');
      }
    }
  },

A mutação do registro trata do processo de registro adicionando os novos dados do usuário ao banco de dados. Embora a mutação de login gerencie logins de usuários – na autenticação bem-sucedida, ela gerará um token JWT, bem como retornará uma mensagem de sucesso na resposta.

  10 coisas que você pode fazer com o Apresentações Google

Agora inclua o resolvedor de consulta para recuperar dados do usuário. Para garantir que esta consulta seja 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 função de administrador.

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') {
          throw new ('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);
        throw new Error('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 a mutação de registro para adicionar novos dados do usuário ao banco de dados e, em seguida, a mutação de login 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.