
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
/usersFaç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
/usersporém, com o http Headerx-access-token : meutokenaquiCom 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 UsuarioSchemadefinimos a estrutura, com os campousernameepassword.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.passwordforem 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
momentpra dizer ‘daqui a 7 dias’ e var token, onde criamos o token comjwt.encode(). Note a variávelsegredodefinida 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-tokenem 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