Eu sempre tive uma treta séria com autenticação. Quando precisei autenticar em uma API REST, então, aí ferrou! Mas existe uma luz ao fim do túnel, e ela se chama JSON Web Token, ou JWT.
Não vou entrar em detalhes da minha briga com métodos de autenticar - no momento, mas acredite quando digo que testei e usei vários. Eis o por que do JWT.
Analisando a Demanda:
Necessidade: Gerenciar rotas protegidas por login e níveis de usuário de uma API REST em NodeJS.
Requisitos: Não depender de sessão (rest é sessionless, lembra?), permitir identificar o usuário (e seu nível), não utilizar Providers de terceiros, integrar com qualquer plataforma frontend.
JSON Web Token: o que é JWT?
JWT pronuncia-se ‘JõT’.
É um jeito simples de encaminhar informações que podem ser confiadas, pois são assinadas e confirmadas.
Isso é um JWT:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo |
São 3 partes, separadas por um .
, encodadas por base64 individualmente:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded assinatura>
Mas atenção: JWT não encripta o payload, apenas o assina. O que isso quer dizer é que você não deve enviar informações confidenciais por JWT. Apenas informações que devem ser verificadas/confiadas.
Por exemplo: Não é uma boa idéia passar uma senha por JWT, mas enviar o ID do usuário APÓS o login, vai permitir que ele faça requisições autenticadas :D, saca só:
Fluxo de uso de JWT para um client
Quero acessar uma rota protegida, tipo
/users
Faço o login, enviando (de forma segura, por https) usuário e senha.
Login com sucesso, recebo o ‘TOKEN’. Guardo pra usar depois ;)
Faço a requisição que eu queria na
/users
porém, com o http Headerx-access-token : meutokenaqui
Com o token enviado, o servidor me identifica e autoriza (ou não) meu acesso, retornando a resposta à minha requisição.
Fluxo de uso de JWT para o SERVER
Se tentarem acessar uma rota protegida sem token, não autoriza.
Se tentar acessar com um token, verifica se é válido. Se for, consegue identificar o usuário pelo TOKEN e libera o acesso.
Quando um usuário faz login, geramos um token, para ele poder fazer requisições autenticadas.
Nossa, Rômulo, que legal, cara! Agora me diz, como eu faço isso?
Como fazer a autenticação de uma API REST com Json Web Token em NodeJs?
Bom, pra explicar legal mesmo a gente vai começar junto, e de mãos dadas, iremos até o fim :P (aperta o cinto, porque vai ser longa a jornada)
O resultado de nossa empreitada esta neste repositório do github
À partir de agora, serei seu guia pelo lindo mundo das APIs com NodeJS, e faremos:
Um servidorzinho
Algumas rotas: POST E GET de usuarios, POST de login
Conexão com o MongoDB do Mongolabs
Criar usuários
Encriptar senhas
Implementar JWT durante o login
Passar middleware que tornará a rota passível de autenticação. E vai conferir o JWT
Vamos lá?
0) Requisitos:
NodeJs
POSTMAN, para testar a API e métodos GET e POST
MongoLabs um db gratuito resolve
1) Servidor em NodeJs.
Já com o Node instalado, vamos criar uma nova pasta.
Nela, crie um arquivo chamado package.json
, insira:
1 | { |
Já pode acessar a pasta pelo terminal, e instalar as dependências, com o comando:
1 | $ npm install |
Enquanto você espera instalar, veja o que cada item faz:
O express
é o framework para App. Vai gerenciar nossas rotas e middlewares.
O body-parser
, antigamente incluso no express, vai parsear nossas requisições, permitindo que usemos urls complexas e trabalhar com formulários.
O mongoose
é o ORM que vai interagir com o MongoDb pra gente.
O bcrypt-nodejs
vai encriptar nossa senha. NUNCA salve a senha sem encriptar.
O jwt-simple
vai gerar e conferir nossos JSON Web Token (ou você achou que ia fazer todo aquele processo acima na unha? hehe)
O moment
vai nos ajudar a trabalhar com data de expiração do JWT.
Agora, criemos o server.js
1 | var express = require('express') |
Note que a var db
é a url do nosso banco de dados. Vc pode criar uma conta grátis no MongoLab, acessá-la, clicar em create database nomeie teste-jwt
deixando com a opção single node (gratis).
Após criada, clique nela, aí em create user e crie um usuário para o nosso DB.
A url deve ser algo como mongodb://usuario:senha@ds053668.mongolab.com:53668/teste-jwt
Após colocar a url na var db
, salve o arquivo, acesse a pasta com o terminal e rode:1
2$ node server
conectado a porta 8080
Sinal que deu boa ^_^
2) Criando algumas rotas
Logo abaixo de /*Aqui criaremos as rotas*/
, insira:
1 | (...) |
Crie um arquivo chamado rotas.js
na mesma pasta.
1 | module.exports = { |
Salvando tudo, você pode executar node server
novamente, fazer um get para http://localhost:8080/api/usuarios e depois um post para conferir se funcionou
(Pra isso, use algo como o postman (clique para baixar)) ou faz no curl se for macho(a).
3) Criando o Model usuário
Crie o arquivo ModelUsuario.js
, na mesma pasta.
1 | var mongoose = require('mongoose'); |
…
…Vish.
Acima temos 3 coisas acontecendo. Nesse arquivo, usamos o mongoose e o bcrypt.
var UsuarioSchema
Primeiro,var UsuarioSchema
definimos a estrutura, com os campousername
epassword
.UsuarioSchema.pre(‘save’….
Depois, setamos uma função.pre('save')
nessa var que criamos:UsuarioSchema.pre('save', function(......
.
Toda vez que formos salvar, essa função.pre()
verifica:- SE(if) o password não foi alterado. Se ele não foi alterado, retorna
next()
, ou seja: continua o fluxo do save().
Se acontecer outra coisa com o password (criado, alterado..) ele gera o salt combcrypt.genSalt()
. Se der boa, ele usa obcrypt.hash()
e encripta nosso password com o salt q ele gerou. Se der erro, retornanext(err)
. Se não, chama onext()
- SE(if) o password não foi alterado. Se ele não foi alterado, retorna
UsuarioSchema.methods.verificaSenha = function(senha, next){}
Aí estamos criando um metodo chamado verificaSenha(), que recebe a senha, usa obcrypt.compare()
. Retorna next(isMatch) se der certo.
E por último module.exports exporta o model com o nome ‘Usuario’.
Pra facilitar estamos colocando tudo na mesma pasta. O ideal, seria ter uma pasta /model
, mas não vamos fazer isso nesse artigo.
Agora já temos nosso model, precisamos criar a funçaõ que cria um novo usuário… aff tá cansando né?
O controller criaUsuario()
Esse cara vai criar nossos usuários. Basicamente ele pega os campos username
e password
, que receberemos na rota, e insere no banco de dados.
Crie o arquivo controllerCriaUsuario.js
1 | var Model = require('./ModelUsuario'); |
Maneiro. Aí estamos apenas gerando um new Model
e depois dando um .save()
nele. Se der tudo certo, recebemos as informações do novo usuario.
Vamos rapidinho alí no rotas.js
pra usar esse controller na postUsuarios!
1 | (...), |
Agora podemos criar nosso usuário com senha encriptada
rode node server
.
Usando o postman, ou outro, faça um POST para http://localhost:8080/api/usuarios envie username e password devidamente preenchidos:
Aeeee conseguimos! Muito legal né? Um servidor express com rotas, criando usuário e salvando no DB com a senha encriptada!
Precisamos agora, fazer o login. E, ao fazer o login, criar o TOKEN
4) O Controller de Login com gerador de Token
Crie o arquivo controllerLogin.js:
1 | var Usuario = require('./ModelUsuario'); |
Vamos pedir que a nossa rota (/login)
rode-o:
1 | (...), |
Reinicie o servidor com1
$ node server
Agora mude a url para http://localhost:8080/api/login e post seu username e password.
Viu? Que massa? Autenticamos e recebemos o token! Dividi o código acima em 3 partes principais.
Antes, da //1, setamos as dependencias que usaremos neste arquivo com os require. Depois usamos um SE o req.body.username
ou o req.body.password
forem vazios, res.send(401)
. Se tiverem preenchidos, prosseguimos para:
Usuario.findOne()
Buscamos por usuários com os dados informados. Se der erro,res.end(401)
. Se não der erro, continua, retornando o resultado comouser
.user.verificaSenha lembra do método que criamos no
ModelUsuario
? pois é. Se ele não retornar o parâmetro isMatch,res.end(401)
neles!var expires: usamos o
moment
pra dizer ‘daqui a 7 dias’ e var token, onde criamos o token comjwt.encode()
. Note a variávelsegredo
definida no começo do arquivo. Vamos precisar dela mais pra frente :P
5) ULTIMA PARTE: O saudoso Middleware para conferir o token e fazer a segurança das rotas
Vamos criar um middleware de nível route para interceptar as requisições nas rotas protegidas e conferir o TOKEN. Basicamente, ele vai testar o token, se der certo, chama next()
, senão: res.end(401)
neles!
Crie o arquivo validarJWT.js
1 | var model = require('./ModelUsuario') |
No //1 SE existir um token, rodamos a funçaõ jwt.decode()
.
No //2 , usamos o resultado do jwt.decode()
para verificar se o token expirou.
no //3 pesquisamos no MongoDB pelo _id que veio no JWT
no //4 ficamos de olho por erros, caso aconteça, 401 neles.
Agora iremos inserir o validarJWT nas rotas à proteger isso será feito em nosso server.js
. Iremos proteger apenas a rota .get()
do /usuarios
:
1 | (...) |
Novamente não é boa prática usar o require
como parâmetro de função. Pelo bem do fluxo do tutorial e arquivo final, estou fazendo, mas o ideal seria criar var validarJWT = require('./validarJWT')
….
Anyway, reinicie o server, faça um GET
para http://localhost:8080/api/usuarios . Viu: Ele disse “TOKEN NAO ENCONTRADO”
Tudo pronto!!! Vamos usar nosso token :D
Dessa vez não precisa nem reiniciar o server. Faça um POST
para http://localhost:8080/api/login com seu username
e password
.
1 | { |
Vamos copiar o token (sim, ele inteiro) e adicionar como header x-access-token
em nossa próxima requisição.
Faça um GET
para /api/usuarios
novamente, dessa vez com o token recebido como header chamada x-access-token
:
Parabéns e obrigado à você que acompanhou até aqui :P
Espero poder ter ajudado. Se tiver dicas, dúvidas ou observações, por favor comente :)
O resultado de nossa empreitada esta neste repositório do github
Comments