Autenticações com JavaScript Web Tokens

jairocket

Jailson Anjos

Posted on November 14, 2022

Autenticações com JavaScript Web Tokens

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.

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 e redireciona para a rota na qual serão mostradas as informações solicitadas.

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

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);
      }
    }
}
Enter fullscreen mode Exit fullscreen mode

No backend, mais precisamente no controller 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" 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.

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'});
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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]);
Enter fullscreen mode Exit fullscreen mode

Antes de consultar e retornar as informações requisitadas, um 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.

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();
}
Enter fullscreen mode Exit fullscreen mode

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.

<BrotherhoodsSection brotherhoods={brotherhoods} />
<EventsSection events={events} />
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
jairocket
Jailson Anjos

Posted on November 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Autenticações com JavaScript Web Tokens
javascript Autenticações com JavaScript Web Tokens

November 14, 2022