Criar uma senha forte que você possa realmente lembrar não é fácil. Os usuários que usam senhas exclusivas para diferentes sites (o que é ideal) têm maior probabilidade de esquecer suas senhas. Portanto, é fundamental adicionar um recurso que permita aos usuários redefinir a senha com segurança quando a esquecerem.
Este artigo é sobre como você pode construir um recurso seguro de redefinição de senha com Node.js e Express .
Primeiro, para acompanhar este tutorial, aqui estão alguns requisitos a serem observados:
- Você deve ter um conhecimento básico de Javascript e Node.js
- Você tem o NodeJS instalado ou pode baixar e instalar a versão mais recente do Node.js
- Você tem o banco de dados MongoDB instalado ou pode baixar e instalar o banco de dados MongoDB. Outra opção é criar uma conta no site MongoDB e configurar um banco de dados gratuito
Agora, vamos começar.
Criação de um fluxo de trabalho para redefinição de senha
O fluxo de trabalho para redefinir sua senha pode assumir diferentes formas e tamanhos, dependendo de quão utilizável e seguro você deseja que seu aplicativo seja.
Neste artigo, implementaremos um design padrão de redefinição de senha segura. O diagrama abaixo mostra como o fluxo de trabalho do recurso funcionará.
- Solicitar redefinição de senha se o usuário existir
- Validar as informações do usuário
- Forneça informações de identificação
- Redefinir a senha ou rejeitar a solicitação de verificação de senha
Construindo um projeto para redefinição de senha
Vamos criar um projeto simples para demonstrar como o recurso de redefinição de senha pode ser implementado. Observe que você pode encontrar o projeto concluído na redefinição de senha com Node.js no GitHub , ou você também pode pular para a seção de redefinição de senha deste tutorial.
Vamos primeiro inicializar nosso projeto com o gerenciador de pacotes npm.
Execute npm init
em seu Terminal/Prompt de Comando e siga as instruções para configurar o projeto.
Estrutura de pastas e arquivos em seu projeto
Nossa estrutura de pastas será semelhante a esta:
- Controladores
auth.controller.js
- Serviços
auth.service.js
- modelos
user.model.js
token.model.js
- Rotas
index.route.js
- Utils
- Emails
- modelo
requestResetPassword.handlebars
resetPassword.handlebars
sendEmail.js
- modelo
- Emails
index.js
db.js
package.json
Dependências para configurar seu projeto com redefinição de senha
Execute o código abaixo para instalar as dependências que usaremos neste projeto com o npm.
npm install bcrypt, cors, dotenv, express, express-async-errors, guiador, jsonwebtoken, mongoose, nodemailer, nodemon
Usaremos:
-
bcrypt
para hash de senhas e redefinir tokens -
cors
para desativar o Compartilhamento de recursos de origem cruzada, pelo menos para fins de desenvolvimento -
dotenv
para permitir que nosso processo de nó tenha acesso às variáveis de ambiente -
express-async-errors
para capturar todos os erros assíncronos para que toda a nossa base de código não fique cheia de try-catch -
guidão
como um mecanismo de modelagem para enviar e-mails em HTML -
mongoose
como um driver para fazer a interface com o banco de dados MongoDB -
nodemailer
para enviar e-mails -
nodemon
para reiniciar o servidor quando um arquivo muda
Configurar variáveis de ambiente
Algumas variáveis variam em nosso aplicativo dependendo do ambiente em que estamos executando nosso aplicativo. Para essas variáveis, vamos adicioná-las às nossas variáveis de ambiente.
Crie um arquivo .env
em seu diretório raiz e cole as seguintes variáveis dentro. Estamos adicionando um sal bcrypt, nosso url de banco de dados, um JWT_SECRET
e um url de cliente. Você verá como os usaremos à medida que prosseguirmos.
BCRYPT_SALT=10 DB_URL=mongodb://127.0.0.1: 27017/testDB JWT_SECRET=mfefkuhio3k2rjkofn2mbikbkwjhnkj CLIENT_URL=localhost://8090
Conectando-se ao banco de dados MongoDB
Vamos criar uma conexão com nosso MongoDB. Este código deve estar em seu arquivo db.js
no diretório raiz.
db.js
const mongoose=require ("mongoose"); let DB_URL=process.env.DB_URL;//aqui estamos usando o Url do MongoDB que definimos em nosso arquivo ENV module.exports=conexão de função assíncrona () { tentar { aguarde mongoose.connect ( DB_URL, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true, autoIndex: true, }, (erro)=> { if (erro) retorna um novo erro ("Falha ao conectar ao banco de dados"); console.log ("conectado"); } ); } catch (erro) { console.log (erro); } };
Configurando o aplicativo Express
Vamos construir nosso ponto de entrada do aplicativo e disponibilizá-lo na porta 8080. Copie e cole no seu arquivo index.js
no diretório raiz.
require ("express-async-errors"); require ("dotenv"). config (); const express=require ("express"); const app=express (); conexão const=requer ("./db"); const cors=require ("cors"); porta const=8080; (função assíncrona db () { aguardar conexão (); }) (); app.use (cors ()); app.use (express.json ()); app.use ("/api/v1", require ("./routes/index.route")); app.use ((erro, req, res, próximo)=> { res.status (500).json ({erro: mensagem de erro}); }); app.listen (porta, ()=> { console.log ("Ouvindo a porta", porta); }); module.exports=app;
Configurando token e modelos de usuário
Precisamos criar um modelo de usuário e token para configurar nosso sistema de redefinição de senha.
1. Modelo de token
Nosso modelo de token terá um tempo de expiração de cerca de 1 hora. Nesse período, espera-se que o usuário conclua o processo de redefinição de senha, caso contrário, o token será excluído do banco de dados. Com o MongoDB, você não precisa escrever código adicional para fazer isso funcionar. Basta definir expira
em seu campo de data, assim:
createdAt: { tipo: data, padrão: Date.now, expira: 3600,//este é o tempo de expiração },
Esta é a aparência de nosso modelo.
Você deve adicionar este código ao arquivo token.model.js
dentro do diretório do modelo.
models/token.model.js
const mongoose=require ("mongoose"); esquema const=mongoose.Schema; const tokenSchema=new Schema ({ ID do usuário: { tipo: Schema.Types.ObjectId, obrigatório: verdadeiro, ref:"usuário", }, símbolo: { tipo: String, obrigatório: verdadeiro, }, criado em: { tipo: data, padrão: Date.now, expira: 3600,//este é o tempo de expiração em segundos }, }); module.exports=mongoose.model ("Token", tokenSchema);
2. Modelo de usuário
O modelo do usuário descreverá como os dados do usuário serão salvos.
E, é claro, antes de discutir a criação de um mecanismo seguro de redefinição de senha, precisamos garantir que estamos criando e armazenando uma senha segura em primeiro lugar.
Lembre-se de que armazenar senhas em texto simples é uma prática ruim e nunca deve ser feito. Em vez disso, use um algoritmo unilateral seguro e forte para hash e armazenar senhas, de preferência uma com um salt. Em nosso caso, usaremos bcrypt , conforme mostrado no código abaixo.
Usaremos bcrypt para fazer hash de senhas para que até nós, como os administradores não saberiam as senhas-você não deveria saber as senhas dos seus usuários e será quase impossível descobrir o contrário, caso o banco de dados seja sequestrado.
const mongoose=require ("mongoose"); const bcrypt=require ("bcrypt"); esquema const=mongoose.Schema; const bcryptSalt=process.env.BCRYPT_SALT; const userSchema=new Schema ( { nome: { tipo: String, trim: true, obrigatório: verdadeiro, único: verdadeiro, }, o email: { tipo: String, trim: true, único: verdadeiro, obrigatório: verdadeiro, }, senha: {type: String}, }, { timestamps: true, } ); userSchema.pre ("salvar", função assíncrona (próxima) { if (! this.isModified ("senha")) { retornar próximo (); } const hash=await bcrypt.hash (this.password, Number (bcryptSalt)); this.password=hash; Próximo(); }); module.exports=mongoose.model ("usuário", userSchema);
Antes que a senha seja salva, usamos o gancho pré-salvo do MongoDB para fazer o hash da senha primeiro nesta parte do código. Estamos usando um salt de 10, conforme mostrado em nosso arquivo .env
, porque isso é forte o suficiente para reduzir a possibilidade de bandidos adivinharem as senhas dos usuários.
userSchema.pre ("salvar", função assíncrona (próximo) { if (! this.isModified ("senha")) { retornar próximo (); } const hash=await bcrypt.hash (this.password, Number (bcryptSalt)); this.password=hash; Próximo(); });
Criar serviços para o processo de redefinição de senha
Teremos três serviços para iniciar e processar completamente nosso processo de redefinição de senha.
1. Serviço de inscrição
Claro, não podemos redefinir a senha de um usuário se ele não tiver uma conta. Portanto, este serviço criará uma conta para um novo usuário.
O código abaixo deve estar no arquivo service/auth.service.js
:
const JWT=require ("jsonwebtoken"); const User=require ("../models/User.model"); Const Token=require ("../models/Token.model"); const sendEmail=require ("../utils/email/sendEmail"); const crypto=require ("crypto"); const bcrypt=require ("bcrypt"); inscrição const=assíncrona (dados)=> { deixe o usuário=esperar User.findOne ({email: data.email}); if (usuário) { lance novo erro ("Email já existe"); } usuário=novo usuário (dados); token const=JWT.sign ({id: user._id}, JWTSecret); esperar user.save (); return (data={ userId: user._id, email: user.email, nome: usuário.nome, token: token, }); };
2. Serviço de solicitação de redefinição de senha
Seguindo nosso fluxo de trabalho, a primeira etapa é solicitar um token de redefinição de senha, certo?
Então, vamos começar por aí. No código abaixo, verificamos se o usuário existe. Se o usuário existir, verificamos se existe um token existente que foi criado para este usuário. Se houver, excluímos o token.
const user=await User.findOne ({email}); if (! usuário) { lançar novo erro ("O usuário não existe"); } deixe o token=esperar o Token.findOne ({userId: user._id}); if (token) { aguardar token.deleteOne () };
Agora, preste atenção a esta parte: vamos criar um novo token aleatório usando a API de criptografia Node.js. Este é o token que enviaremos ao usuário:
let resetToken=crypto.randomBytes (32).toString ("hex");
Agora, crie um hash deste token, que salvaremos no banco de dados porque salvar resetToken simples em nosso banco de dados é o mesmo que salvar senhas em texto simples, e isso irá anular todo o propósito de configurar uma senha segura redefinir.
No entanto, enviaremos o token simples para o e-mail dos usuários conforme mostrado no código abaixo e, na próxima seção, verificaremos o token e permitiremos que criem uma nova senha.
service/auth.service.js
const JWT=require ("jsonwebtoken"); const User=require ("../models/User.model"); Const Token=require ("../models/Token.model"); const sendEmail=require ("../utils/email/sendEmail"); const crypto=require ("crypto"); const bcrypt=require ("bcrypt"); const requestPasswordReset=async (email)=> { const user=await User.findOne ({email}); if (! usuário) lança novo erro ("O usuário não existe"); deixe o token=esperar o Token.findOne ({userId: user._id}); if (token) aguarda token.deleteOne (); deixe resetToken=crypto.randomBytes (32).toString ("hex"); const hash=await bcrypt.hash (resetToken, Number (bcryptSalt)); aguardar novo Token ({ userId: user._id, token: hash, createdAt: Date.now (), }).Salve (); const link=`$ {clientURL}/passwordReset? token=$ {resetToken} & id=$ {user._id}`; sendEmail (user.email,"Solicitação de redefinição de senha", {name: user.name, link: link,},"./template/requestResetPassword.handlebars"); link de retorno; };
O link de redefinição de senha contém o token e a ID do usuário, que serão usados para verificar a identidade do usuário antes que ele possa redefinir a senha.
Ele também contém o URL do cliente. O domínio raiz em que o usuário terá que clicar para continuar o processo de redefinição.
const link=`$ {clientURL}/passwordReset? token=$ {resetToken} & id=$ {user._id}`;
Você pode encontrar a função sendEmail
no repositório GitHub aqui e o modelo de e-mail aqui . Estamos usando nodemailer e mecanismo de modelagem de guidão para enviar o e-mail.
1. Serviço de redefinição de senha
Oh, olhe! Recebi um e-mail para confirmar que fiz a solicitação de redefinição de senha. Agora vou clicar no link a seguir.
Depois de clicar no link, você será direcionado à página de redefinição de senha. Você pode construir esse front-end com qualquer tecnologia que desejar, pois esta é apenas uma API.
Neste ponto, enviaremos de volta o token
, uma nova senha
e uma id de usuário
para verificar o usuário e criar uma nova senha posteriormente.
Aqui está o que estaremos enviando de volta para a API de redefinição de senha será semelhante a:
{ "token":"4f546b55258a10288c7e28650fbebcc51d1252b2a69823f8cd1c65144c69664e", "userId":"600205cc5fdfce952e9813d8", "senha":"kjgjgkflgk.hlkhol" }
Será processado pelo código abaixo.
service/auth.service.js
const resetPassword=async (userId, token, senha)=> { deixe passwordResetToken=aguarde Token.findOne ({userId}); if (! passwordResetToken) { lançar um novo erro ("token de redefinição de senha inválida ou expirada"); } const isValid=await bcrypt.compare (token, passwordResetToken.token); if (! isValid) { lançar um novo erro ("token de redefinição de senha inválida ou expirada"); } const hash=await bcrypt.hash (senha, número (bcryptSalt)); esperar User.updateOne ( {_id: userId}, {$ set: {password: hash}}, {novo: verdadeiro} ); const user=await User.findById ({_id: userId}); enviar email( user.email, "Redefinição de senha com sucesso", { nome: usuário.nome, }, "./template/resetPassword.handlebars" ); esperar passwordResetToken.deleteOne (); return true; };
Observe como estamos usando bcrypt para comparar o token que o servidor recebeu com a versão em hash no banco de dados?
const isValid=await bcrypt.compare (token, passwordResetToken.token);
Se eles forem iguais, prosseguiremos com o hash da nova senha…
const hash=await bcrypt.hash (senha, número (bcryptSalt));
… e atualize a conta do usuário com a nova senha.
aguarda User.updateOne ( {_id: userId}, {$ set: {password: hash}}, {novo: verdadeiro} );
Para manter o usuário informado a cada etapa, enviaremos um e-mail para confirmar a alteração da senha e excluir o token do banco de dados.
Até agora, conseguimos criar os serviços para inscrever um novo usuário e redefinir sua senha.
Bravo.
Controladores para serviços de redefinição de senha
Aqui estão os controladores para cada um desses serviços. Os controladores coletam dados do usuário, enviam-nos aos serviços para processar os dados e, em seguida, devolvem o resultado ao usuário.
controllers/auth.controller.js
const { inscrever-se, requestPasswordReset, resetPassword, }=requer ("../services/auth.service"); const signUpController=async (req, res, next)=> { const signupService=espera inscrição (req.body); return res.json (signupService); }; const resetPasswordRequestController=async (req, res, next)=> { const requestPasswordResetService=await requestPasswordReset ( req.body.email ); return res.json (requestPasswordResetService); }; const resetPasswordController=async (req, res, next)=> { const resetPasswordService=await resetPassword ( req.body.userId, req.body.token, req.body.password ); return res.json (resetPasswordService); }; module.exports={ signUpController, resetPasswordRequestController, resetPasswordController, };
Parabéns, você criou um recurso seguro de redefinição de senha para seus usuários.
O que você pode fazer a seguir?
Aqui estão algumas coisas que você pode fazer para tornar este recurso de redefinição de senha mais seguro.
- Você pode pedir aos usuários que forneçam respostas para suas perguntas de segurança (eles já devem ter criado essas perguntas e respostas).
- Use serviços de hospedagem seguros. Um serviço de hospedagem inseguro pode criar um backdoor para você como administrador. Se o seu serviço de hospedagem não estiver funcionando direito, os bandidos podem comprometer seu próprio sistema, mesmo que ele pareça seguro.
- Implemente a autenticação de dois fatores (2FA) com o Google Authenticator ou SMS. Dessa forma, mesmo que o e-mail do usuário tenha sido comprometido, o hacker também precisará ter o telefone do usuário.
- Não armazene as senhas dos usuários. É estressante ter que redefinir sua senha toda vez que você a esquece. Em vez disso, você pode permitir que seus usuários façam login com serviços já existentes, como Facebook, Github, GMail e semelhantes. Dessa forma, eles não precisarão se lembrar da senha nunca mais. Eles só precisarão se preocupar com suas senhas nesses sites de terceiros.
Se quiser brincar com o projeto concluído, você pode conferir no GitHub .
Conclusão
A segurança digital não é uma piada. Os hackers estão sempre em busca de novas maneiras de hackear sistemas, então você deve estar sempre em busca de novas maneiras de dificultar a vida deles.
Seus usuários confiam em você para proteger seus dados e você não quer decepcioná-los. De que outras formas você recomendaria para tornar um recurso de redefinição de senha mais seguro? Obrigado por ler.
A postagem Implementando uma redefinição de senha segura no Node.js apareceu primeiro no LogRocket Blog .