Vamos começar instalando o Express e uma biblioteca para fazer a criação e validação do token JWT:

npm i express jsonwebtoken dayjs uuid

Agora vamos criar o nosso serviço com um endpoint que queremos muito proteger:

// server.js
const express = require("express")

const app = express()

app.use(express.json())

app.get("/secret", (req, res) => {
  return res.json({
    message: "uma mensagem super secreta que deve ser protegida"
  })
})

app.listen(4000, () => {
  console.log(`Listening on port 4000`)
})

O primeiro passo vai ser criar uma rota para o login:


const dayjs = require("dayjs")

app.post("/login", (req, res) => {
  const { username, password } = req.body

  // Isso e so um exemplo, aqui entraria alguma chamada para o seu banco de dados
  if (username !== "admin" || password !== "admin") {
    return res.status(401).json({
      message: "Invalid credentials"
    })
  }

  const token = jwt.sign({
    username,
    // Isso aqui define uma data de expiração para o seu token, ele irá expirar em um dia (24h)
    exp: dayjs().add(1, "day").unix()
  }, "secret")

  return res.json({
    token
  })
})

Para testar isso com httpie podem-mo executar o seguinte comando:

 http POST localhost:4000/login username=admin password=admin
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjgxODUwOTA2LCJpYXQiOjE2ODE3NjQ1MDZ9.fK8sQn0vqjFuQT7mq9fTnzuN2CARVOlH1mRCVTDZl6M"
}

Em implementações com tokens JWT o front-end irá armazenar esse token no browser e envia-lo junto a todas as requisições que fizer na sua API.

Para validar essas requisições vamos usar um middleware do express:

function verifyToken(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "")?.trim()

  if (!token) {
    return res.status(401).json({
      message: "Token missing"
    })
  }

  try {
    // Já valida o segredo e data de expiração do token, além de ja devolver o token decodificado
    const payload = jwt.verify(token, "secret")
    req.user = payload
    return next()
  } catch {
    return res.status(401).json({
      message: "Invalid token"
    })
  }
}

Agora é so adicionar esse middleware na nossa rota:

app.get("/secret", verifyToken, (req, res) => {
  return res.json({
    message: "uma mensagem super secreta que deve ser protegida"
  })
})

Testando com um token válido:

http GET localhost:4000/secret "Authorization:Bearer eyJhb..."

{
  "message": "uma mensagem super secreta que deve ser protegida"
}

Ok, mas e se o usuario se deslogar antes das 24h o token continua valido certo? Sim, geralmente apenas descarta-mos o token no front-end! Essa é uma solução bem simples e geralmente amais recomendada, mas se isso não for possivel por alguma regra de negocio também podemos criar uma blocklist que invalide o token.

Par aisso vamos precisar fazer algumas alterações no nosso código e no funcionamento da nossa autenticação atual:

  1. Agora os tokens vão ter também um id, uma “claim” que sera um identificador unico para aquele token, vamos adicionar isso no login
  2. Criar um rota de logout, que ira armazenar esse ID, uma boa ideia e salvar no redis com um TTL assim os dados somem depois de um tempo, mas pode salvar em um banco de dados comum, como aqui estou só brincando e escrevendo um post rápido, vou salvar em um Array, jamais faça isso em produção
  3. Vamos precisar validar esse id contra uma lista de tokens que foram invalidados, o acesso so deve ser liberado se o token não existir nessa lista

O login será bem simples de alterar, vamos adicionar uma “claim” jti, ou JWT ID, com um valor aleatorio:

const { v4: uuidv4 } = require('uuid');

const token = jwt.sign({
  username,
  exp: dayjs().add(1, "day").unix(),
  jti: uuidv4()
}, "secret")

A rota de logou será mais ou menos assim:

const blocklist = []
app.delete("/logout", verifyToken,(req, res) => {
  blocklist.push(req.user.jti)
  return res.json({
    message: "Logout"
  })
})

Só lembrando mais uma vez: Não salve nada em variavel global em um código de verdade, isso e apenas um exercicio, e possivelmente depois eu vou fazer um post usando redis.

E ao verificar o token adicionamos mais uma condição:

const payload = jwt.verify(token, "secret")
if (blocklist.includes(payload.jti)) {
  return res.status(401).json({
    message: "Token invalid"
  })
}
req.user = payload
return next()

Testando a rota de logout:

http DELETE localhost:4000/logout "Authorization:Bearer eyJh..."
{
  "message": "Logout"
}

E agora chamando esse endpoint depois de chamar o logout:

http GET localhost:4000/secret "Authorization:Bearer eyJh..."
{
  "message": "Token invalid"
}

Este é um post super rapidinho para cumprir um desafio de escrever posts por 30 dias, você pode ver outros posts na tag 30daysOfPosts