GraphQL - Cliente de autenticação

Autenticação é o processo ou ação de verificar a identidade de um usuário ou processo. É importante que um aplicativo autentique um usuário para garantir que os dados não estejam disponíveis para um usuário anônimo. Nesta seção, aprenderemos como autenticar um cliente GraphQL.

JWT expresso

Neste exemplo, usaremos jQuery para criar um aplicativo cliente. Para autenticar solicitações, usaremos express-jwt módulo no lado do servidor.

O módulo express-jwt é um middleware que permite autenticar solicitações HTTP usando tokens JWT. JSON Web Token (JWT) é uma longa string que identifica o usuário conectado.

Depois que o usuário efetua login com êxito, o servidor gera um token JWT. Este token identifica distintamente um log. Em outras palavras, o token é uma representação da identidade do usuário. Portanto, da próxima vez, quando o cliente chegar ao servidor, ele terá que apresentar esse token para obter os recursos necessários. O cliente pode ser um aplicativo móvel ou um aplicativo da web.

Ilustração

Seguiremos um procedimento passo a passo para entender esta ilustração.

Configurando o Servidor

A seguir estão as etapas para configurar o servidor -

Etapa 1 - Baixe e instale as dependências necessárias para o projeto

Crie uma pasta auth-server-app. Mude seu diretório para auth-server-app do terminal. Siga as etapas 3 a 5 explicadas no capítulo Configuração do ambiente.

Etapa 2 - Criar um esquema

Adicionar schema.graphql arquivo na pasta do projeto  auth-server-app e adicione o seguinte código -

type Query
{
   greetingWithAuth:String
}

Etapa 3 - Adicionar resolvedores

Crie um arquivo resolvers.js na pasta do projeto e adicione o seguinte código -

O resolvedor verificará se um objeto de usuário autenticado está disponível no objeto de contexto do GraphQL. Isso gerará uma exceção se um usuário autenticado não estiver disponível.

const db = require('./db')

const Query = {
   greetingWithAuth:(root,args,context,info) => {

      //check if the context.user is null
      if (!context.user) {
         throw new Error('Unauthorized');
      }
      return "Hello from TutorialsPoint, welcome back : "+context.user.firstName;
   }
}

module.exports = {Query}

Etapa 4 - Criar arquivo Server.js

O middleware de autenticação autentica chamadores usando um JSON Web Token. O URL para autenticação é http://localhost:9000/login.

Esta é uma pós-operação. O usuário deve enviar seu e-mail e senha que serão validados pelo backend. Se um token válido for gerado usando o método jwt.sign, o cliente terá que enviá-lo no cabeçalho para solicitações subsequentes.

Se o token for válido, req.user será definido com o objeto JSON decodificado para ser usado por middleware posterior para autorização e controle de acesso.

O código a seguir usa dois módulos - jsonwebtoken e express-jwt para autenticar solicitações -

  • Quando o usuário clica no greetbotão, uma solicitação para a rota / graphql é emitida. Se o usuário não estiver autenticado, ele será solicitado a se autenticar.

  • O usuário é apresentado a um formulário que aceita id de e-mail e senha. Em nosso exemplo, a rota / login é responsável por autenticar o usuário.

  • A rota / login verifica se uma correspondência é encontrada no banco de dados para as credenciais fornecidas pelo usuário.

  • Se as credenciais forem inválidas, uma exceção HTTP 401 será retornada ao usuário.

  • Se as credenciais forem válidas, um token será gerado pelo servidor. Este token é enviado como parte da resposta ao usuário. Isso é feito pela função jwt.sign.

const expressJwt = require('express-jwt');
const jwt = require('jsonwebtoken');

//private key
const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64');

app.post('/login', (req, res) => {
   const {email, password} = req.body;
   
   //check database
   const user = db.students.list().find((user) =>  user.email === email);
   if (!(user && user.password === password)) {
      res.sendStatus(401);
      return;
   }
   
   //generate a token based on private key, token doesn't have an expiry
   const token = jwt.sign({sub: user.id}, jwtSecret);
   res.send({token});
});

Para cada solicitação, a função app.use () será chamada. Isso, por sua vez, invocará o middleware expressJWT. Este middleware irá decodificar o JSON Web Token. O ID do usuário armazenado no token será recuperado e armazenado como um usuário de propriedade no objeto de solicitação.

//decodes the JWT and stores in request object
app.use(expressJwt({
   secret: jwtSecret,
   credentialsRequired: false
}));

Para disponibilizar a propriedade do usuário dentro do contexto GraphQL, esta propriedade é atribuída ao context objeto como mostrado abaixo -

//Make req.user available to GraphQL context
app.use('/graphql', graphqlExpress((req) => ({
   schema,
   context: {user: req.user &&apm; db.students.get(req.user.sub)}
})));

Crio server.js no caminho da pasta atual. O arquivo server.js completo é o seguinte -

const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');
const expressJwt = require('express-jwt'); //auth
const jwt = require('jsonwebtoken'); //auth
const db = require('./db');

var port = process.env.PORT || 9000
const jwtSecret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64');
const app = express();

const fs = require('fs')
const typeDefs = fs.readFileSync('./schema.graphql',{encoding:'utf-8'})
const resolvers = require('./resolvers')
const {makeExecutableSchema} = require('graphql-tools')

const schema = makeExecutableSchema({typeDefs, resolvers})

app.use(cors(), bodyParser.json(), expressJwt({
   secret: jwtSecret,
   credentialsRequired: false
}));

const  {graphiqlExpress,graphqlExpress} = require('apollo-server-express')

app.use('/graphql', graphqlExpress((req) => ({
   schema,
   context: {user: req.user && db.students.get(req.user.sub)}
})));
app.use('/graphiql',graphiqlExpress({endpointURL:'/graphql'}))

//authenticate students
app.post('/login', (req, res) => {
   const email = req.body.email;
   const password = req.body.password;

   const user = db.students.list().find((user) =>  user.email === email);
   if (!(user && user.password === password)) {
      res.sendStatus(401);
      return;
   }
   const token = jwt.sign({sub: user.id}, jwtSecret);
   res.send({token});
});

app.listen(port, () => console.info(`Server started on port ${port}`));

Etapa 5 - Executar o aplicativo

Execute o comando  npm start no terminal. O servidor estará instalado e funcionando na porta 9000. Aqui, usamos GraphiQL como um cliente para testar o aplicativo.

Abra o navegador e digite o URL http://localhost:9000/graphiql. Digite a seguinte consulta no editor -

{
   greetingWithAuth
}

Na resposta abaixo, ocorreu um erro porque não somos um usuário autenticado.

{
   "data": {
      "greetingWithAuth": null
   },
   "errors": [
      {
         "message": "Unauthorized",
         "locations": [
            {
               "line": 2,
               "column": 3
            }
         ],
         "path": [
            "greetingWithAuth"
         ]
      }
   ]
}

Na próxima seção, vamos criar um aplicativo cliente para autenticação.

Configurando o cliente JQuery

No aplicativo cliente, um botão de saudação é fornecido para invocar o esquema greetingWithAuth. Se você clicar no botão sem fazer login, será exibida a mensagem de erro abaixo -

Depois de fazer o login com um usuário disponível no banco de dados, a seguinte tela aparecerá -

Acessar greeting, precisamos primeiro acessar o URL http://localhost:9000/login rota como abaixo.

A resposta conterá o token gerado a partir do servidor.

$.ajax({
   url:"http://localhost:9000/login",
   contentType:"application/json",
   type:"POST",
   data:JSON.stringify({email,password}),
   success:function(response) {
      loginToken = response.token;
      $('#authStatus')
      .html("authenticated successfully")
      .css({"color":"green",'font-weight':'bold'});
      $("#greetingDiv").html('').css({'color':''});
   },
   error:(xhr,err) =>  alert('error')
})

Após um login bem-sucedido, podemos acessar o esquema greetingWithAuth conforme mostrado abaixo. Deve haver um cabeçalho de autorização para todas as solicitações subsequentes com token de portador.

{ 
   url: "http://localhost:9000/graphql",
   contentType: "application/json",
   headers: {"Authorization": 'bearer '+loginToken},  type:'POST',
   data: JSON.stringify({
   query:`{greetingWithAuth}`
}

A seguir está o código para index.html -

<!DOCTYPE html>
<html>
   <head>
      <script src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script>
         $(document).ready(function() {
            let loginToken = "";
            $("#btnGreet").click(function() {
                  $.ajax({url: "http://localhost:9000/graphql",
                  contentType: "application/json",
                  headers: {"Authorization": 'bearer '+loginToken},
                  type:'POST',
                  data: JSON.stringify({
                  query:`{greetingWithAuth}` }),
                  success: function(result) {
                  $("#greetingDiv").html("<h1>"+result.data.greetingWithAuth+"</h1>")
                  },
                  error:function(jQxhr,error) {
                     if(jQxhr.status == 401) {
                        $("#greetingDiv").html('please authenticate first!!')
                        .css({"color":"red",'font-weight':'bold'})
                        return;
                     }
                     $("#greetingDiv").html('error').css("color","red");
                  }
               });
            });
            $('#btnAuthenticate').click(function() {
               var email =  $("#txtEmail").val();
               var password =  $("#txtPwd").val();
               if(email && password) {
                  $.ajax({
                     url:"http://localhost:9000/login",
                     contentType:"application/json",
                     type:"POST",
                     data:JSON.stringify({email,password}),
                     success:function(response) {
                        loginToken =  response.token;
                        $('#authStatus')
                        .html("authenticated successfully")
                        .css({"color":"green",'font-weight':'bold'});
                        $("#greetingDiv").html('').css({'color':''});
                     },
                     error:(xhr,err) =>  alert('error')
                  })
               }else alert("email and pwd empty")
            })
         });
      </script>
   </head>
   
   <body>
      <h1> GraphQL Authentication </h1>
      <hr/>
      <section>
         <button id = "btnGreet">Greet</button>
         <br/> <br/>
         <div id = "greetingDiv"></div>
      </section>
      <br/> <br/> <br/>
      <hr/>
      
      <section id = "LoginSection">
         <header>
            <h2>*Login first to  access greeting </h2>
         </header>
         <input type = "text" value = "[email protected]" placeholder = "enter email" id = "txtEmail"/>
         <br/>
         
         <input type = "password" value = "pass123" placeholder = "enter password" id = "txtPwd"/>
         <br/>
         
         <input type = "button" id = "btnAuthenticate"  value = "Login"/>
         <p id = "authStatus"></p>
      </section>
   </body>
</html>