Autenticações com JavaScript Web Tokens
Jailson Anjos
Posted on November 14, 2022
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);
}
}
}
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'});
}
}
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]);
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();
}
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} />
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:
- Cliente faz requisição, enviando suas credenciais;
- Servidor recebe requisição, confere as credenciais e, em caso de sucesso, assina um novo token e o devolve ao cliente;
- No nosso exemplo, o token é recebido pelo cliente e salvo como cookie no navegador;
- Quando o precisa acessar informações protegidas, o cliente busca o cookie no navegador e o envia nos headers da requisição ao servidor;
- 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!
Posted on November 14, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.