141 lines
3.4 KiB
Markdown
141 lines
3.4 KiB
Markdown
# 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`:
|
|
|
|
```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)
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|