Autenticações com JavaScript Web Tokens
Tags: javascript, jwt, node, react
Published: 2022-11-14 14:07:20

Esse artigo foi publicado originalmente em 07 de setembro de 2021, no meu perfil do Linkedin. Pouco mais de um ano depois, após realizar algumas melhorias no código, resolvi publicar uma versão atualizada. Neste texto, você vai encontrar um exemplo de implementação de controle de acesso de dados utilizando _jwt_, tanto pela ótica do _frontend_, quanto do _backend_, utilizando _Reactjs_ e _Nodejs_, respectivamente. Para melhor compreensão, é preciso conhecer noções básicas do protocolo para requisições [HTTP](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Messages).

Como é fácil de imaginar, o controle de acesso a determinadas informações é essencial no processo de desenvolvimento web e mobile. É preciso garantir que as informações sejam acessadas apenas pelas pessoas autorizadas. Uma das ferramentas utilizadas para alcançar este objetivo é o _Javascript Web Token_, ou _jwt_.

O _jwt_ provê um meio de troca de informações entre o cliente e o servidor de forma segura, por meio da assinatura de um _token_, usando pares de chaves públicas/privadas. Esse processo permite certificar que a parte que possui a chave privada é quem assinou o _token_. Para informações mais aprofundadas, acesse aqui https://jwt.io/introduction .

Descrevo abaixo os procedimentos que realizei para implementar esta funcionalidade no meu projeto de rede social para amantes de vinhos.

## Autenticação por meio da assinatura do _token_

Para fazer login, é preciso fazer uma requisição _POST_ com as credenciais da pessoa (email e password, neste caso). O servidor verifica no banco de dados se as credenciais estão corretas. Em caso positivo, um _token_ é assinado e enviado para o cliente, com validade de 20 minutos. O _token_ recebido é salvo no navegador como _[cookie](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Cookies)_ e redireciona para a rota na qual serão mostradas as informações solicitadas.

A função "handleSubmit", do componente <Login />, é chamada quando clicamos em "Enviar", após preenchermos email e senha:

```js
import { FormEvent, useState } from 'react'
import { useNavigate } from "react-router-dom";
import axios from "axios";
import Cookie from "js-cookie";

async function handleSubmit(e: FormEvent){
  e.preventDefault();
  try {
    const result = await axios.post("http://localhost:3333/login", {
      email,
      password,
    });
    Cookie.set("token", result.data.token);
    navigate("/dashboard");
  } catch (error: any) {
      if (error.response.data.message === "E-mail não cadastrado!") {
        setEmailError(error.response.data.message);
        }
      if (error.response.data.message === "Senha incorreta!") {
        setPasswordError(error.response.data.message);
      }
    }
}
```
No _backend_, mais precisamente no _[controller](https://www.devmedia.com.br/introducao-ao-padrao-mvc/29308)_ do usuário, a assinatura do _token_ é realizada pelo método _"userLogger"_. Para tanto, em primeiro lugar, o método _"validationResult"_ da biblioteca _["express-validator"](https://www.npmjs.com/package/express-validator)_ verifica se os dados recebidos pela requisição _POST_ estão no formato correto. Após, se existe alguma pessoa cadastrada no banco de dados com as credenciais informadas. Havendo sucesso nestas validações, o _token_ é assinado e enviado como resposta, junto com os dados solicitados pelo cliente. Se estas validações falharem, uma mensagem de erro é enviada para o cliente.

```js
const { validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const db = require("../database/models");
const jwt = require("jsonwebtoken");

const UserController = {
  userLogger: async(req, res) => {
    const errorsList = validationResult(req);
    if(errorsList.isEmpty()){
      const { email, password } = req.body;
      const usr = await db.User.findOne({
        where: { email }
      });
    if ((usr.mail === email) && (bcrypt.compareSync(password, usr.password))){
      const profile = {
        id: usr.id,
        name: usr.name,
        surname: usr.surname,
        description: usr.description,
        email: usr.email,
        avatar_picture: usr.avatar_picture
      }
      const token = jwt.sign({id: profile.id}, process.env.SECRET, {expiresIn: "20m"});
      return res.json({auth: true, token, profile})
    } else {
      return res.status(500).json({message: 'Login Inválido'});
  }
}
```
## Validação do _token_ 

Para acessar as informações privadas, é preciso que o servidor verifique se o _token_ é válido. Para tanto, é necessário incluir o _token_ - que está salvo como cookie neste caso - nos headers da requisição _GET_ pela qual se pretende se obter as informações.

```js
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Cookies from "js-cookie";

export function Dashboard() {
  const [parameter, setParameter] = useState("");
  const [user, setUser] = useState({
    name: "",
    surname: "",
    description: "",
    avatar_picture: "",
    background_picture: "",
  });
  const [brotherhoods, setBrotherhoods] = useState([]);
  const [events, setEvents] = useState([]);
  const token = Cookies.get("token");
  const navigate = useNavigate();

  useEffect(()=>{
    fetch('http://localhost:3333/dashboard', {
      headers: { authorization: `Bearer ${token}`}
    }).then(
      response => response.json()
     ).then(
        response => {
          setUser(response.User);
          setBrotherhoods(response.brotherhoods);
          setEvents(response.events)
        }).catch(
            error => navigate('/login')
          );
  }, [token, navigate]);
```

Antes de consultar e retornar as informações requisitadas, um [_middleware_](https://www.redhat.com/pt-br/topics/middleware/what-is-middleware) é responsável por verificar se o _token_ recebido no _headers_ da requisição é válido. Em caso negativo, o acesso às informações é negado.

```js
const jwt = require("jsonwebtoken");
  module.exports = (req, res, next) => {
    const token = req.headers.authorization.split(" ")[1];
    const decoded =jwt.verify(token, process.env.SECRET, function (err, decoded) {
     if(err){
       return res.status(401).send({
         mensagem: "Sessão expirada. Por favor, logue novamente"
       });
     return decoded;
  });
  req.headers.authorization = decoded;

  next();
}
```

## Renderização da informação privada na interface do cliente

Utilizando os hooks _useState_ e _useEffect_ a informação recebida como resposta da requisição é atribuída a variáveis e enviadas aos respectivos componentes como propriedades para que possam ser renderizados.

```js
<BrotherhoodsSection brotherhoods={brotherhoods} />
<EventsSection events={events} />
```
Neste artigo te convidei a acompanhar todo o processo de assinatura e validação de um _JavaScript Web Token_, desde a requisição do cliente até a resposta do servidor. Podemos resumir o fluxo da seguinte forma:

1. Cliente faz requisição, enviando suas credenciais;
2. Servidor recebe requisição, confere as credenciais e, em caso de sucesso, assina um novo _token_ e o devolve ao cliente;
3. No nosso exemplo, o _token_ é recebido pelo cliente e salvo como _cookie_ no navegador;
4. Quando o precisa acessar informações protegidas, o cliente busca o _cookie_ no navegador e o envia nos headers da requisição ao servidor;
5. Servidor valida se o token é válido e se foi assinado pelo cliente que está requisitando a informação. Em caso positivo, os dados solicitados são enviados, caso contrário, é enviada uma mensagem solicitando que um novo login seja realizado, reiniciando o ciclo.
 
Se você chegou até aqui, agradeço sua atenção! Você implementaria esta funcionalidade de forma diferente? Conhece uma maneira de tornar esse código mais eficiente? Tem alguma dúvida? Adoraria obter o seu feedback.

Até a próxima!