Files
catalogo/jwtimplementation.md

3.4 KiB

Guia didatico: validar JWT nos servicos consumidores (sem JWKS)

Este guia mostra a forma mais simples de integrar os servicos:

  1. Auth assina o token com JWT_PRIVATE_KEY_PEM
  2. cada servico consumidor valida localmente com JWT_PUBLIC_KEY_PEM

1) Configuracao em cada servico consumidor

Cada servico precisa ter no .env:

JWT_ISSUER="https://auth.local"
JWT_AUDIENCE="internal-apis"
JWT_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"

Importante:

  • o JWT_PUBLIC_KEY_PEM deve ser exatamente a chave publica do Auth
  • mantenha os \n literais no .env

2) O que validar

Para toda rota protegida:

  1. ler Authorization
  2. extrair Bearer <token>
  3. validar assinatura RS256 com JWT_PUBLIC_KEY_PEM
  4. validar iss, aud e exp
  5. validar se sub existe
  6. montar request.auth = { id: sub, token }

Se algo falhar, retornar 401.

3) Exemplo pratico (Node/Express + jose)

import express from "express";
import { importSPKI, jwtVerify } from "jose";

const app = express();

const ISSUER = process.env.JWT_ISSUER!;
const AUDIENCE = process.env.JWT_AUDIENCE!;
const PUBLIC_KEY_PEM = process.env.JWT_PUBLIC_KEY_PEM!.replace(/\\n/g, "\n");

let publicKeyPromise: ReturnType<typeof importSPKI> | null = null;

function getPublicKey() {
  if (!publicKeyPromise) {
    publicKeyPromise = importSPKI(PUBLIC_KEY_PEM, "RS256");
  }

  return publicKeyPromise;
}

function authMiddleware() {
  return async (req, res, next) => {
    try {
      const authHeader = req.header("authorization");

      if (!authHeader) {
        return res
          .status(401)
          .json({ message: "Missing Authorization header" });
      }

      const [scheme, token] = authHeader.split(" ");

      if (!scheme || !token || scheme.toLowerCase() !== "bearer") {
        return res
          .status(401)
          .json({ message: "Authorization must be Bearer token" });
      }

      const publicKey = await getPublicKey();

      const { payload } = await jwtVerify(token, publicKey, {
        issuer: ISSUER,
        audience: AUDIENCE,
        algorithms: ["RS256"],
      });

      if (typeof payload.sub !== "string" || !payload.sub.trim()) {
        return res.status(401).json({ message: "Invalid token claims" });
      }

      req.auth = {
        id: payload.sub,
        token,
      };

      return next();
    } catch {
      return res
        .status(401)
        .json({ message: "Invalid or expired access token" });
    }
  };
}

app.get("/profile/me", authMiddleware(), (req, res) => {
  return res.json({ userId: req.auth.id });
});

4) Propagacao entre servicos (A -> B)

Quando API A chamar API B em nome do usuario:

  • repasse o mesmo Authorization: Bearer <token>
  • API B valida esse token com JWT_PUBLIC_KEY_PEM
  • API B usa sub como identidade confiavel
const authHeader = req.header("authorization");

const response = await fetch("http://courses-service/courses/me", {
  headers: { Authorization: authHeader! },
});

5) Checklist rapido

  • todos os servicos com o mesmo JWT_PUBLIC_KEY_PEM
  • middleware valida assinatura + iss + aud + exp
  • sub obrigatorio
  • retorna 401 para token ausente/invalido/expirado
  • usa sub como ID confiavel

6) Trade-off dessa abordagem

Vantagem:

  • muito simples para implementar e explicar

Desvantagem:

  • quando trocar a chave publica, precisa atualizar o .env de todos os servicos consumidores