O autor do artigo, cuja tradução estamos publicando hoje, diz que agora você pode observar a crescente popularidade de serviços de autenticação como Google Firebase Authentication, AWS Cognito e Auth0. Soluções genéricas como passport.js se tornaram o padrão do setor. Mas, dada a situação atual, tornou-se comum que os desenvolvedores nunca entendam completamente quais mecanismos estão envolvidos na operação dos sistemas de autenticação.
Este material é dedicado ao problema de organizar a autenticação do usuário no Node.js. Nele, em um exemplo prático, são consideradas a organização do registro do usuário no sistema e a organização de sua entrada no sistema. Isso levantará questões como trabalhar com a tecnologia JWT e a representação do usuário.

Além disso, preste atenção
neste repositório GitHub, que contém o código para o projeto Node.js. Alguns exemplos são fornecidos neste artigo. Você pode usar este repositório como base para suas próprias experiências.
Requisitos do projeto
Aqui estão os requisitos para o projeto com os quais trataremos aqui:
- A presença de um banco de dados no qual o endereço de e-mail e a senha do usuário serão armazenados, clientId e clientSecret, ou algo como uma combinação de chaves públicas e privadas.
- Usando um algoritmo criptográfico forte e eficiente para criptografar uma senha.
No momento em que estou escrevendo este material, acredito que o melhor dos algoritmos criptográficos existentes é o Argônio2. Peço que você não use algoritmos criptográficos simples como SHA256, SHA512 ou MD5.
Além disso, sugiro que você dê uma olhada
neste material maravilhoso, no qual você pode encontrar detalhes sobre a escolha de um algoritmo para hash de senhas.
Registro de usuários no sistema
Quando um novo usuário é criado no sistema, sua senha deve ser hash e armazenada no banco de dados. A senha é armazenada no banco de dados juntamente com o endereço de e-mail e outras informações sobre o usuário (por exemplo, entre eles podem estar o perfil do usuário, a hora do registro e assim por diante).
import * as argon2 from 'argon2'; class AuthService { public async SignUp(email, password, name): Promise<any> { const passwordHashed = await argon2.hash(password); const userRecord = await UserModel.create({ password: passwordHashed, email, name, }); return {
As informações da conta do usuário devem se parecer com o seguinte.
Dados do usuário recuperados do MongoDB usando o Robo3TLogin do Usuário
Aqui está um diagrama das ações executadas quando um usuário tenta efetuar login.
Login do UsuárioAqui está o que acontece quando um usuário faz login:
- O cliente envia ao servidor uma combinação do identificador público e da chave privada do usuário. Geralmente, é um endereço de e-mail e senha.
- O servidor procura o usuário no banco de dados por endereço de email.
- Se o usuário existir no banco de dados, o servidor fará o hash da senha enviada a ele e comparará o que aconteceu com o hash da senha armazenado no banco de dados.
- Se a verificação for bem-sucedida, o servidor gerará o chamado token ou autenticação - JSON Web Token (JWT).
JWT é uma chave temporária. O cliente deve enviar essa chave ao servidor com cada solicitação ao terminal autenticado.
import * as argon2 from 'argon2'; class AuthService { public async Login(email, password): Promise<any> { const userRecord = await UserModel.findOne({ email }); if (!userRecord) { throw new Error('User not found') } else { const correctPassword = await argon2.verify(userRecord.password, password); if (!correctPassword) { throw new Error('Incorrect password') return { user: { email: userRecord.email, name: userRecord.name, }, token: this.generateJWT(userRecord), }
A verificação de senha é realizada usando a biblioteca argon2. Isso é para evitar os chamados "
ataques de tempo ". Ao realizar esse ataque, um invasor tenta decifrar a senha com força bruta, com base em uma análise de quanto tempo o servidor precisa para formar uma resposta.
Agora vamos falar sobre como gerar JWT.
O que é um JWT?
JSON Web Token (JWT) é um objeto JSON codificado no formato de sequência. Os tokens podem ser tomados como substitutos dos cookies, o que tem várias vantagens sobre eles.
O token consiste em três partes. Este é o cabeçalho, a carga útil e a assinatura. A figura a seguir mostra sua aparência.
JwtOs dados do token podem ser decodificados no lado do cliente sem o uso de uma chave ou assinatura secreta.
Isso pode ser útil para transferir, por exemplo, metadados codificados dentro do token. Esses metadados podem descrever a função do usuário, seu perfil, a duração do token e assim por diante. Eles podem ser projetados para uso em aplicativos front-end.
Aqui está a aparência de um token decodificado.
Token decodificadoGerando JWT no Node.js
Vamos criar a função
generateToken
necessária para concluir o trabalho no serviço de autenticação do usuário.
Você pode criar o JWT usando a biblioteca jsonwebtoken. Você pode encontrar esta biblioteca em npm.
import * as jwt from 'jsonwebtoken' class AuthService { private generateToken(user) { const data = { _id: user._id, name: user.name, email: user.email }; const signature = 'MySuP3R_z3kr3t'; const expiration = '6h'; return jwt.sign({ data, }, signature, { expiresIn: expiration }); }
A coisa mais importante aqui são os dados codificados. Não envie informações secretas do usuário em tokens.
Uma assinatura (aqui está a constante de
signature
) são os dados secretos usados para gerar o JWT. É muito importante garantir que a assinatura não caia nas mãos erradas. Se a assinatura for comprometida, o invasor poderá gerar tokens em nome dos usuários e roubar suas sessões.
Endpoint Protection e validação de JWT
Agora, o código do cliente precisa enviar um JWT em todas as solicitações para um terminal seguro.
É recomendável que você inclua JWT nos cabeçalhos da solicitação. Eles geralmente são incluídos no cabeçalho da autorização.
Cabeçalho da AutorizaçãoAgora, no servidor, você precisa criar um código que seja middleware para rotas expressas. Coloque este código no arquivo
isAuth.ts
:
import * as jwt from 'express-jwt';
É útil poder obter informações completas sobre a conta do usuário no banco de dados e anexá-las à solicitação. No nosso caso, esse recurso é implementado usando o middleware do arquivo
attachCurrentUser.ts
. Aqui está o seu código simplificado:
export default (req, res, next) => { const decodedTokenData = req.tokenData; const userRecord = await UserModel.findOne({ _id: decodedTokenData._id }) req.currentUser = userRecord; if(!userRecord) { return res.status(401).end('User not found') } else { return next(); }
Após a implementação desse mecanismo, as rotas poderão receber informações sobre o usuário que está executando a solicitação:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import ItemsModel from '../models/items'; export default (app) => { app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => { const user = req.currentUser; const userItems = await ItemsModel.find({ owner: user._id }); return res.json(userItems).status(200); })
A rota de
inventory/personal-items
agora está protegida. Para acessá-lo, o usuário deve ter um JWT válido. Além disso, uma rota pode usar as informações do usuário para pesquisar no banco de dados as informações necessárias.
Por que os tokens estão protegidos contra invasores?
Após ler sobre o uso do JWT, você pode se perguntar: “Se os dados do JWT puderem ser decodificados no lado do cliente, é possível processar o token de forma a alterar o ID do usuário ou outros dados?”.
Decodificação de token - a operação é muito simples. No entanto, você não pode "refazer" esse token sem ter essa assinatura, esses dados secretos que foram usados ao assinar o JWT no servidor.
É por isso que a proteção desses dados confidenciais é tão importante.
Nosso servidor verifica a assinatura no middleware isAuth. A biblioteca express-jwt é responsável pela verificação.
Agora, depois de descobrirmos como a tecnologia JWT funciona, vamos falar sobre alguns recursos adicionais interessantes que ela nos fornece.
Como se passar por um usuário?
A representação de usuário é uma técnica usada para efetuar login em um sistema como um usuário específico sem saber sua senha.
Esse recurso é muito útil para superadministradores, desenvolvedores ou equipe de suporte. A representação permite que eles resolvam problemas que aparecem apenas no decorrer dos usuários que trabalham com o sistema.
Você pode trabalhar com o aplicativo em nome do usuário sem saber sua senha. Para fazer isso, basta gerar um JWT com a assinatura correta e com os metadados necessários que descrevem o usuário.
Crie um terminal que possa gerar tokens para entrar no sistema sob o disfarce de usuários específicos. Somente o superadministrador do sistema pode usar esse terminal.
Para iniciantes, precisamos atribuir a esse usuário uma função com um nível de privilégio mais alto do que outros usuários. Isso pode ser feito de várias maneiras diferentes. Por exemplo, basta adicionar o campo de
role
às informações do usuário armazenadas no banco de dados.
Pode parecer com o mostrado abaixo.
Novo campo nas informações do usuárioO valor do campo de
role
super-admin
é
super-admin
.
Em seguida, você precisa criar um novo middleware que verifique a função do usuário:
export default (requiredRole) => { return (req, res, next) => { if(req.currentUser.role === requiredRole) { return next(); } else { return res.status(401).send('Action not allowed'); }
Ele deve ser colocado após isAuth e attachCurrentUser. Agora crie o terminal que gera o JWT para o usuário em nome do qual o superadministrador deseja efetuar login:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import roleRequired from '../middlwares/roleRequired'; import UserModel from '../models/user'; export default (app) => { app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => { const userEmail = req.body.email; const userRecord = await UserModel.findOne({ email: userEmail }); if(!userRecord) { return res.status(404).send('User not found'); return res.json({ user: { email: userRecord.email, name: userRecord.name }, jwt: this.generateToken(userRecord) }) .status(200); })
Como você pode ver, não há nada misterioso. O superadministrador sabe o endereço de email do usuário em nome do qual você deseja fazer login. A lógica do código acima é uma reminiscência de como o código funciona, fornecendo uma entrada para o sistema de usuários comuns. A principal diferença é que a senha não é verificada aqui.
A senha não é verificada aqui devido ao fato de que simplesmente não é necessária aqui. A segurança do endpoint é fornecida pelo middleware.
Sumário
Não há nada errado em confiar em serviços e bibliotecas de autenticação de terceiros. Isso ajuda os desenvolvedores a economizar tempo. Mas eles também precisam conhecer os princípios nos quais a operação dos sistemas de autenticação se baseia e o que garante o funcionamento desses sistemas.
Neste artigo, exploramos as possibilidades da autenticação JWT, discutimos a importância de escolher um bom algoritmo criptográfico para o hash de senhas. Examinamos a criação de um mecanismo de representação do usuário.
Fazer o mesmo com algo como passport.js está longe de ser fácil. A autenticação é um tópico enorme. Talvez voltemos a ela.
Caros leitores! Como você cria sistemas de autenticação para seus projetos Node.js.