Básico de JWT com Node
SrWalkerB
Posted on May 31, 2021
Eae pessoal, tudo tranquilo? Espero que sim, hoje pretendo introduzir de forma simples e prática uma API com JWT (Json web tokens) com Node usando framework Express.
Porque usar Tokens?
Vamos imaginar o seguinte cenário: Digamos que você tem uma aplicação que só pode ser acessada por um usuário que esteja logado no seu sistema, como você sabe que aquele usuário realmente efetuou o login? Ou como vamos retornar os dados que de fato são daquele usuário? Para isso precisamos de alguma coisa que prove que aquele usuário tem permissão para acessar determinada rota e que identifique quem é ele na nossa aplicação, para resolvermos esse problema vamos usar tokens! Nas nossas rotas vamos obrigar aquele usuário passar um token válido que só é dado no momento que é feito o login, e nesse token existe uma coisa chamada payload que são alguns dados que estão dentro do token e que podemos ter acesso posteriormente. Um token tem essa cara aqui:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjFzZGZhYWZjYWFzZHNkLWRheGNhc2Rhc2QtZGFzZGFzZCIsImlhdCI6MTYyMjIzNjQwOCwiZXhwIjoxNjIyMjU0NDA4fQ.v4XVzOVQ-BAF4xeQ-iHNEeq__hqCZzjs3kc13DO7YDI
Beleza, mas como vamos saber se o token é válido?
Na hora de criarmos o token passamos uma chave secreta que só o servidor conhece, essa chave é quem válida se o token é válido ou não, ou seja, mesmo que alguém mal intencionado crie um token falso, ele não teria acesso a nossas rotas pois ele não conhece qual chave secreta nosso servidor usa para criar os tokens.
Tá bom tá bom, me mostre como se faz
Vamos criar uma estrutura simples com express, separando nossos componentes:
import dotenv from "dotenv"
import express from "express"
import userRoutes from "./routes/userRoutes"
dotenv.config()
const app = express()
app.use(express.json())
app.use(userRoutes)
export default app
Criei um arquivo de UserControllers com uma classe do mesmo nome:
import { Request, Response } from "express"
import TokensOptions from "../helpers/TokensOptions"
const fakeDatabase = {
id: "1sdfaafcaasdsd-daxcasdasd-dasdasd",
email: "any_email@gmail.com",
password: "any_password",
}
export default new class UserControllers{
auth(req: Request, resp: Response){
try {
const { email, password } = req.body
if(email != fakeDatabase.email || password != fakeDatabase.password){
return resp.status(404).json({ message: "user not found" })
}
return resp.status(200).json({ message: "success" })
} catch (error) {
console.log(error)
return resp.status(500).json({ message: "error not expect" })
}
}
profile(req: Request, resp: Response){
try {
return resp.status(200).json({ message: "my profile" })
} catch (error) {
console.log(error)
return resp.status(500).json({ message: "error not expect" })
}
}
}
E importando o controller no arquivo de “userRoutes”
import { Router } from "express";
import UserControllers from "../controllers/UserControllers";
const userRoutes = Router()
userRoutes.get("/profile", UserControllers.profile)
userRoutes.post("/auth", UserControllers.auth)
export default userRoutes
E por fim vamos criar o nosso servidor, em um arquivo com nome de “server.ts”:
import app from "../src/app";
const PORT = 4560
app.listen(PORT, () => {
console.log(`Servidor rodando, PORT: ${PORT}`)
})
Por fim teremos uma estrutura assim:
Até aqui nada de novo, agora vamos implementar o sistema de tokens. Queremos que essa rota de “/profile” seja uma rota privada e que só usuários autenticados possam acessar, até o momento ela está aberta a qualquer um, precisamos verificar se o usuário passou ou não um token, essa verificação precisa acontecer antes de chegar ao nosso controller, e para isso vamos usar um middleware, que vai verificar se aquele token é válido ou não, mas para isso precisamos criar um arquivo que vai fazer isso. Então, vamos criar um arquivo chamado “TokensOptions.ts” e colocar dentro de uma pasta chamada helpers.
import { Request } from "express"
import jwt from "jsonwebtoken"
export default new class TokenOptions{
generateToken(id: string){
return jwt.sign({ id: id }, process.env.TOKEN_KEY!, { expiresIn: "5h" })
}
verifyToken(token: string): any{
return jwt.verify(token, process.env.TOKEN_KEY!, (err, data) => {
if(err){
return { message: "invalid token" }
}
return { message: data }
})
}
getToken(req: Request){
return req.header("Authorization")?.replace("Bearer ", "")
}
}
Nesse arquivo criamos uma classe e alguns métodos: gerador de tokens, verificador de tokens e por último um método para pegar os tokens, como vamos usar o tipo “Bearer” ele vem junto com o token que o usuário passa, usamos o replace para remover ele. Perceba que no método “generatedToken” estamos passando o id do usuário que será gravado no nosso token, depois passamos nossa chave secreta via variáveis de ambiente que pode ser qualquer nome, e por último chamamos o parâmetro chamado “expiresIn” usamos ele para passar o tempo em que aquele token vai permanecer válido, depois disso ele vai expirar, também temos um método para verificar se o token é válido, por isso usamos nossa chave secreta. Agora vamos criar o middleware dentro da pasta middlewares:
import { Request, Response, NextFunction } from "express"
import TokensOptions from "../helpers/TokensOptions"
const autentication = (req: Request, resp: Response, next: NextFunction) => {
const token = TokensOptions.getToken(req)
const verifyToken = TokensOptions.verifyToken(token!)
if(!token || verifyToken.message == "invalid token"){
return resp.status(401).json({ message: "Unauthorized" })
}
next()
}
export {
autentication
}
Aqui estamos verificando se o token é válido e se o usuário passou um token, se tudo estiver ok ele passa e chama o “next()” indo direto para nosso controller.
No nosso arquivo de rotas vamos chamar esse middleware:
import { Router } from "express";
import UserControllers from "../controllers/UserControllers";
import { autentication } from "../middlewares/autentication";
const userRoutes = Router()
userRoutes.get("/profile", autentication, UserControllers.profile)
userRoutes.post("/auth", UserControllers.auth)
export default userRoutes
Agora se usarmos algum programa que faz requisições HTTP (postman ou insommnia), e não passarmos um token, teremos um:
Vamos tentar passar algum valor para verificar se realmente está funcionando:
Beleza, agora nossa rota está sendo protegida, mas ainda não temos nenhum token. Numa situação real, você vai querer que um usuário efetue o login e se estiver correto retornamos um token temporário para ele. Então vamos voltar ao nosso UserController e acrescentar nossa classe com o método que cria tokens, primeiro verificamos se email e password existe no nosso banco de dados fake, se existir passamos o id do usuário no payload do token:
auth(req: Request, resp: Response){
try {
const { email, password } = req.body
if(email != fakeDatabase.email || password != fakeDatabase.password){
return resp.status(404).json({ message: "user not found" })
}
const token = TokensOptions.generateToken(fakeDatabase.id)
return resp.status(200).json({ message: token })
} catch (error) {
console.log(error)
return resp.status(500).json({ message: "error not expect" })
}
}
Teremos o seguinte resultado:
Agora se testarmos esse token na nossa rota “/profile”:
Ele retorna com status code 200 e com a mensagem que só é dada se o usuário for válido, ou seja, nosso sistema protegido com tokens está funcionando corretamente.
Mas se precisarmos carregar algum dado do usuário que está em um banco de dados?
Lembra que eu falei que passamos o id do usuário no payload do token? Temos acesso a esse id no retorno do nosso “verifyToken” da classe TokensOptions.ts:
profile(req: Request, resp: Response){
try {
const token = TokensOptions.getToken(req)
const { id } = TokensOptions.verifyToken(token!).message
return resp.status(200).json({ message: `my profile: ${id}` })
} catch (error) {
console.log(error)
return resp.status(500).json({ message: "error not expect" })
}
}
Conclusão
Obrigado por ter lido até aqui, agora você sabe como deixar sua aplicação um pouco mais segura, se tiver alguma dúvida ou achou algum erro escreva nos comentários ou você pode contribuir no repositório do projeto.
Link do repositório: https://github.com/SrWalkerB/article_JWT_Node
Meu linkedIn: https://www.linkedin.com/in/walker-brendo-7331191ab/
Posted on May 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.