Autenticação em Node.js com Passport

Atualizado em 11/11/2020! 

Em outros tutoriais aqui do blog eu já ensinei a fazer CRUDs completos usando Express e diversos bancos de dados, relacionais (como MySQL e SQL Server) e não-relacionais. Ou quase completos, afinal, eu sempre pulo a parte da autenticação.

Verdade seja dita, é difícil um sistema web que não tenha algum tipo de identificação, mesmo que você não veja como uma medida de segurança em si. A Internet é uma espécie de terra sem lei, e mesmo em serviços gratuitos, como os do Google, a autenticação garante que abusos serão evitados ou ao menos controlados.

Dito isso, hoje veremos como fazer autenticação de aplicações web com Node.js usando Passport!

Caso o que esteja procurando seja autenticação de web APIs, sugiro JWT.

Veremos neste tutorial:

  1. A aplicação web
  2. Escolhendo a tecnologia de autenticação
  3. Programando sua estratégia de autenticação
  4. Configurando o projeto para autenticação
  5. Fazendo o login funcionar

Este é um tutorial intermediário e imagino que você já tenha conhecimentos básicos de Node.js e Express. Se preferir, você pode assisti-lo no meu canal do youtube:

Este e outros tutoriais incríveis podem ser vistos em videoaulas construindo aplicações completas em meu curso de Node.js e MongoDB.

Curso FullStack

#1 – A aplicação web

Para ganharmos tempo aqui, usarei o express-generator para criar a aplicação pra gente. Rode os comando abaixo e seja feliz (você vai precisar de permissão de administrador):

Com isso, teremos uma aplicação em Express pronta usando EJS como view-engine e com uma rota (e view) index e outra users.

Primeiro passo de preparação da nossa aplicação para ter autenticação: criar uma tela de login!

Crie na sua aplicação uma nova view chamada views/login.ejs com o seguinte HTML dentro:

Note que deixei um código de servidor verificando a existência de uma message no model, para renderizar uma label de erro ali embaixo. Usaremos isso mais tarde para avisar de erros de autenticação. Também deixei dois links, um para recuperação de senha e outro para cadastro, sendo que mais tarde criaremos estas duas telas e seus funcionamentos.

Agora crie um arquivo novo de rota chamado routes/login.js e inclua o código abaixo para que ele renderize nossa view de login.

E no seu app.js, adicione uma chamada a este arquivo de rota, como abaixo.

Vamos melhorar diversos pontos no decorrer do tutorial, mas rode a sua aplicação e acesse localhost:3000/login e verá a tela de autenticação. Obviamente isso não inibe ninguém de acessar páginas internas através da URL do navegador, mas um problema de cada vez, por favor!

Tela de Login
Tela de Login

Programaremos o seu funcionamento mais pra frente.

#2 – Escolhendo a tecnologia de autenticação

Neste tutorial usarei o módulo Passport, que é o padrão quando o assunto é autenticação em Express, o web framework que é padrão quando o assunto são webapps em Node.js, a plataforma que é padrão quando o assunto… (e assim por diante).

No entanto, Passport está longe de ser uma bala de prata quando o assunto é segurança nas suas aplicações. Passport é apenas um middleware de segurança que exige que os desenvolvedores saibam o que estão fazendo e que programem a segurança do jeito que quiserem, sendo muito flexível, extensível e de uso comum no mercado.

Neste tutorial, tentarei ser o mais profissional possível em termos de segurança considerando que muitos leitores costumam usar os códigos que forneço/ensino em seus sistemas, no entanto, vale a ressalva de sempre ser crítico e cético quanto a códigos da Internet, especialmente aqueles que podem danificar o seu sistema como a camada de segurança.

Outras opções de autenticação existem aos montes na Internet, principalmente as baseadas em padrões web e através de APIs, o que lhe eximem muitas vezes a responsabilidade de uma série de tarefas burocráticas de se ter autenticação em sua aplicação, como autenticar, armazenar senhas, cadastro de usuários, recuperação de senhas, etc. Nomes como Google Account, Microsoft Account, Facebook Authentication, Auth0 e até mesmo Firebase possuem soluções para isso, sem contar Github, Twitter e a lista vai longe. São todas excelentes opções mas que eventualmente te engessam em algumas questões e te torna dependente das empresas em questão.

Sendo assim, optarei aqui pelo Passport não por ele ser a melhor solução de autenticação do mercado, mas por ser aberto, extensível, flexível, etc. E por ter uma boa adesão do mercado quando o assunto é Node.js.

#3 – Programando sua estratégia de autenticação

Agora podemos definir a nossa estratégia de autenticação. Vamos fazer isso em um novo arquivo chamado auth.js na raiz do nosso projeto.

Lembra quando falei que o Passport era bacana por ser extensível? A estratégia de autenticação é completamente configurável, desde os padrões de terceiros, até padrões abertos como OAuth ou o clássico username/password. Aqui vamos instalar o módulo passport-local, que define como estratégia o uso de usuário e senha armazenados em nosso próprio banco para funcionar (clássico username/password):

E vamos também instalar o módulo bcryptjs para manter nossas senhas seguras no banco de dados. Armazenar hashs das senhas ao invés de texto plano no banco de dados é uma política mínima visando segurança das informações.

Agora sim vamos voltar ao nosso auth.js e carregar estes dois módulos, além de definir o nosso module.exports que vai retornar uma função do passport (que é o middleware de autenticação) que criaremos mais pra frente:

Note que criei um array de users e que já deixei um usuário criado, com id 1, username “adm”, o hash da senha “123” e meu email que pode ser útil no futuro. Note também o comentário ali dentro da function exportada: é ali que vamos trabalhar agora.

Primeiro, crie ali dentro a function exportada outras duas functions de busca de usuário em nossa base, que por enquanto vai ser um array in-memory. Precisamos das duas para fazer nossa autenticação funcionar, sendo que a primeira função busca e retorna um usuário por username. Já a segunda busca e retorna por id.:

Por questões de segurança e praticidade ao mesmo tempo, o cookie de autenticação (que falarei melhor mais tarde) não conterá as informações da sessão, as mesmas ficarão no banco (nosso array por enquanto) e teremos apenas o id localmente nos clientes. Assim, precisamos configurar as funções de serialização e desserialização de usuário ainda dentro daquele espaço de exportação desse módulo:

A função done é “nativa” do passport e serve para sinalizar erro e passar informações do usuário para o passport. Na função serializeUser, passamos a chave primária para ser serializado no cookie do usuário. Já no deserializeUser, recebemos o id e vamos com ele no banco de dados retornar o usuário inteiro.

E por fim, vamos definir a estratégia de autenticação em si, chamada de LocalStrategy, ainda no final do espaço do module.exports:

Vamos por partes aqui pois é bastante coisa:

  • usernameField e passwordField são os names dos inputs no form HTML que será postado durante a autenticação;
  • a function findUser é a que criamos anteriormente que vai no banco/array buscar usuário por username;
  • se der erro ou se não existir aquele username, avisamos ao passport que não rola essa autenticação através da função done;
  • se o usuário existir, usaremos o bcrypt.compareSync entre a senha digitada no form de login (password) e a senha salva (hash) no banco de dados;
  • o retorno dessa comparação também é sinalizado ao passport via function done.

Com isso finalizamos nosso módulo com a estratégia de autenticação. Uma versão mais parruda desse módulo poderia contabilizar o número de tentativas erradas para pedir que o usuário solucione um Captcha, bloquear o usuário, avisar o usuário quando um login de novo IP acontecer, etc.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

#4 – Configurando o projeto para autenticação

Basicamente quando um usuário entra na página inicial do seu webapp, ele não está autenticado, está iniciando uma sessão anônima. Para que ele possa entrar nas páginas seguras do seu webapp, ele deve estar autenticado, em uma sessão autenticada.

Essa autenticação ocorre durante o processo de login, onde usando credenciais válidas (usuário e senha, por exemplo), a sessão do nosso usuário recebe um cookie identificando-o. Nos demais acessos, esse cookie é verificado para conceder ou não autorização para navegar entre as páginas.

O uso de cookies e sessões é o jeito mais comum de garantir que um usuário está autenticado em páginas web e é o que usaremos aqui. Antes de mexer com eles, vamos adicionar o módulo passport em nosso projeto, com o comando abaixo (curioso como já falamos bastante nele mas só agora adicionamos no projeto):

Para manipular a sessão de aplicações web feitas usando Express usaremos o módulo express-session, que instalamos junto no comando anterior.

Para uma abordagem mais escalável e profissional, nosso cookie de sessão não vai armazenar todos os dados da sessão, mas sim apenas seu ID, enquanto que os demais dados estarão em nosso banco de dados/array.

O express-session permite armazenar os dados da sua sessão em diferentes mecanismos de persistência, desde que usando os conectores adequados. Por padrão, ele salva em memória mesmo, mas em exemplos futuros posso mostrar o uso desses conectores

Agora vamos chamar algumas das dependências no nosso app.js:

Isso apenas declara e inicia nossos módulos recém instalados. Agora vamos adicionar estas linhas para passar a usá-los no app.js, logo antes dos app.use das suas rotas:

Aqui estou carregando nosso módulo auth.js passando o objeto passport pra ele configurar a estratégia de autenticação. Depois digo para o express-session qual secret ele vai usar para algumas criptografias internas e quanto tempo de vida o cookie vai ter (e consequentemente a sessão).

As variáveis resave e saveUnitialized são configurações adicionais que dizem se a session deve ser salva em todas requisições (resave) e se mesmo usuários anônimos devem ter sessão salva (saveUnitialized).

E com isso deixamos nossa aplicação ‘pronta’ para receber autenticação, embora que se você rodar esse projeto agora, não há segurança alguma ainda.

#5 – Fazendo o login funcionar

Com isso, podemos agora voltar para o arquivo login.js que receberá os posts da tela de login e realizará a verificação de usuário e senha junto ao passport (que vai usar a estratégia que configuramos anteriormente). Vale lembrar que qualquer sistema com o mínimo de segurança deve estar utilizando SSL para que os dados postados pelo formulário não sejam facilmente capturados e lidos na rede (se estiver usando a dica que dei de usar a Umbler ou a AWS, você pode ativar SSL gratuito e de qualidade da Let’s Encrypt ou da CloudFlare).

Primeiro, vamos carregar o passport no login.js, lá no início e vamos alterar a rota GET deste arquivo e adicionar mais uma rota POST:

A primeira, é um GET /login que devolve uma mensagem apropriada conforme a flag de erro na querystring existir ou não. Você pode aperfeiçoar este comportamento para diferentes mensagens de acordo com diferentes códigos de erro ou coisas do tipo.

Já a segunda function captura requisições POST /login passando ao passport a tarefa de autenticar usando a estratégia local. O objeto de configuração passado como segundo parâmetro da função authenticate define as URLs de sucesso e de falha, conforme o resultado que o passport definir. Note que ali o sucesso redireciona para ‘/’ (index), mude para o path adequado da sua aplicação.

Voltando ao app.js, vamos adicionar uma function que vai ser muito útil para não deixar usuários anônimos acessarem as páginas internas da aplicação:

Chamaremos essa função authenticationMiddleware toda vez que uma requisição solicitar uma página que não seja as públicas (como login). Basicamente ela verifica se a requisição está autenticada (isAuthenticated), caso contrário joga o usuário anônimo para a tela de login com a flag de falha.

Agora, precisamos apenas garantir que nenhum usuário anônimo possa acessar a tela inicial do sistema, usando esta função que acabamos de criar como uma intermediária (middleware) entre as requisições para as telas privadas da nossa aplicação. No arquivo app.js ainda, devemos alterar a rota de acesso à página inicial e à users como abaixo (mude de acordo com sua aplicação, mas deixe login sempre primeiro):

Por ora, você pode cadastrar usuários manualmente (ou usar o adm/123 que pré-cadastramos antes) usando alguma ferramenta bcrypt online para gerar o hash da senha.

Depois teste a sua tela de login para ver se ela aceita suas credenciais, se ela redireciona corretamente, tente acessar a tela de chat (ou a tela privada que você definiu) sem fazer login antes ou até mesmo diminua o TTL da sua sessão para ver se ela expira sozinha e te manda de volta para o login.

Erro no Login
Erro no Login

se quiser ver uma implementação com banco de dados, tem essa série de posts aqui no blog de como usar o Passport em conjunto do MongoDB.

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js, onde você encontra este mesmo material em formato de videoaula!


Publicado por

Luiz Duarte

Pós-graduado em computação, professor, empreendedor, autor, Agile Coach e programador nas horas vagas.