Escopo do projeto e decisões de arquitetura
Neste projeto final, você vai implementar uma camada completa de autenticação e autorização em uma API, com requisitos de segurança e qualidade (documentação, checklist e testes). A ideia é consolidar tudo em um fluxo funcional e auditável, com validações e casos de erro bem definidos.
Stack sugerida (adaptável)
- API: Node.js + Express (ou framework equivalente)
- Banco: PostgreSQL (ou equivalente relacional)
- Autenticação: JWT de curta duração + Refresh Token (rotacionado)
- Autorização: RBAC para regras gerais + 1 regra ABAC para um recurso específico
- Proteções: rate limiting, validações, headers de segurança, mitigação de CSRF/XSS quando aplicável
- Testes: suíte mínima de integração para fluxos críticos
Escolha orientada: JWT vs sessão
Para este projeto guiado, a escolha recomendada é JWT com refresh token, porque facilita testar expiração/renovação e cenários de revogação sem depender de estado de sessão no servidor. Se você optar por cookies httpOnly para o refresh token, trate CSRF no endpoint de refresh/logout. Se optar por armazenar refresh token fora de cookie (ex.: header), o risco de XSS aumenta e você deve reforçar a estratégia de armazenamento e CSP no front-end (quando existir).
Modelo de dados (usuários, papéis e tokens)
Tabelas mínimas
Modele as entidades para suportar: cadastro/login, papéis, revogação/rotação de refresh token, auditoria básica e uma regra ABAC.
-- users: identidade e credenciais (hash/salt já tratados pela lib escolhida no código, não no SQL puro aqui) CREATE TABLE users ( id UUID PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'ACTIVE', -- ACTIVE | DISABLED created_at TIMESTAMP NOT NULL DEFAULT now(), updated_at TIMESTAMP NOT NULL DEFAULT now()); -- roles e associação (RBAC) CREATE TABLE roles ( id UUID PRIMARY KEY, name TEXT UNIQUE NOT NULL -- ex.: 'admin', 'editor', 'viewer'); CREATE TABLE user_roles ( user_id UUID REFERENCES users(id) ON DELETE CASCADE, role_id UUID REFERENCES roles(id) ON DELETE CASCADE, PRIMARY KEY (user_id, role_id)); -- refresh tokens: armazenar hash do token, metadados e revogação/rotação CREATE TABLE refresh_tokens ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL, family_id UUID NOT NULL, -- para rotação e detecção de reutilização issued_at TIMESTAMP NOT NULL DEFAULT now(), expires_at TIMESTAMP NOT NULL, revoked_at TIMESTAMP NULL, replaced_by UUID NULL, -- aponta para o novo refresh token (rotação) user_agent TEXT NULL, ip TEXT NULL); CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id); CREATE INDEX idx_refresh_tokens_family ON refresh_tokens(family_id); -- recurso de exemplo para RBAC+ABAC: documents CREATE TABLE documents ( id UUID PRIMARY KEY, owner_id UUID REFERENCES users(id) ON DELETE CASCADE, title TEXT NOT NULL, content TEXT NOT NULL, visibility TEXT NOT NULL DEFAULT 'PRIVATE', -- PRIVATE | ORG | PUBLIC created_at TIMESTAMP NOT NULL DEFAULT now(), updated_at TIMESTAMP NOT NULL DEFAULT now());Regra ABAC escolhida (exemplo)
Além do RBAC (ex.: admin pode tudo; editor pode criar/editar), implemente uma regra ABAC simples e verificável: um usuário pode atualizar um documento se (a) tiver papel editor e (b) for o owner do documento, ou se (c) tiver papel admin. Isso força a checagem de atributo do recurso (owner_id) e do sujeito (roles).
Endpoints do projeto (contrato e códigos de erro)
Defina desde já o contrato de inputs/outputs e códigos de erro. Isso orienta implementação e testes.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Autenticação
| Método | Rota | Descrição | Sucesso | Erros comuns |
|---|---|---|---|---|
| POST | /auth/register | Cadastro | 201 + user | 400 validação, 409 email em uso |
| POST | /auth/login | Login e emissão de tokens | 200 + accessToken (+ refresh) | 400 validação, 401 credenciais inválidas, 423 usuário desabilitado |
| POST | /auth/refresh | Rotaciona refresh e emite novo access | 200 + accessToken (+ novo refresh) | 401 refresh inválido/expirado, 403 reutilização detectada |
| POST | /auth/logout | Revoga refresh atual | 204 | 401 não autenticado |
| GET | /me | Perfil do usuário autenticado | 200 + user | 401 token ausente/inválido |
Recursos protegidos (RBAC + ABAC)
| Método | Rota | Proteção | Sucesso | Erros comuns |
|---|---|---|---|---|
| POST | /documents | RBAC: editor/admin | 201 + document | 401, 403 |
| GET | /documents/:id | Regra de visibilidade + auth opcional | 200 + document | 404, 403 |
| PATCH | /documents/:id | ABAC: editor dono OU admin | 200 + document | 401, 403, 404 |
| DELETE | /documents/:id | RBAC: admin | 204 | 401, 403, 404 |
Formato de erro padronizado
Padronize respostas de erro para facilitar debug e testes.
{ "error": { "code": "VALIDATION_ERROR", "message": "Campo email é obrigatório", "details": [{ "field": "email", "issue": "required" }] }}Implementação passo a passo
Passo 1: estrutura do projeto e camadas
Separe responsabilidades para evitar autorização espalhada e inconsistências.
routes/: definição de rotas e middlewarescontrollers/: parsing de input e resposta HTTPservices/: regras de negócio (login, refresh, autorização)repositories/: acesso a dados (users, tokens, documents)middlewares/: auth, rate limit, validação, tratamento de erros
Passo 2: validação de entrada (register/login)
Implemente validação consistente (ex.: Zod/Joi/class-validator). Regras mínimas: email válido, senha com política definida, campos obrigatórios, limites de tamanho.
// Exemplo (pseudo) de schema const RegisterSchema = { email: "email|required|max:254", password: "string|required|min:12|max:72" };Passo 3: cadastro (register)
- Normalizar email (lowercase, trim)
- Verificar unicidade
- Gerar hash de senha (bcrypt/argon2)
- Criar usuário com status ACTIVE
- Atribuir papel padrão (ex.: viewer)
// Controller (pseudo) POST /auth/register input: { email, password } output 201: { id, email, roles: [...] }Passo 4: login e emissão de tokens
No login, você deve emitir: access token curto (ex.: 10–15 min) e refresh token longo (ex.: 7–30 dias) com rotação.
// Payload sugerido do access token { sub: user.id, roles: ["viewer", "editor"], iat: 1700000000, exp: 1700000900, jti: "uuid" }Decisão de transporte do refresh token (recomendado): cookie httpOnly + SameSite + Secure. O access token pode ir no body e ser enviado no header Authorization nas chamadas subsequentes.
// Resposta 200 (exemplo) { "accessToken": "eyJ...", "tokenType": "Bearer", "expiresIn": 900 }Passo 5: middleware de autenticação (access token)
- Ler
Authorization: Bearer <token> - Validar assinatura e claims obrigatórias
- Carregar contexto do usuário (id e roles do token; opcionalmente checar status no banco)
- Falhar com 401 em token ausente/inválido/expirado
// req.auth = { userId, roles } next()Passo 6: refresh token com rotação e detecção de reutilização
Implemente o endpoint /auth/refresh com as seguintes regras:
- Recebe refresh token (cookie ou body)
- Calcula hash e busca em
refresh_tokensportoken_hash - Verifica: não revogado, não expirado, usuário ativo
- Gera novo refresh token (mesma
family_id) e marca o antigo como revogado ereplaced_byapontando para o novo - Emite novo access token
Detecção de reutilização: se um refresh token já estiver revogado e ainda assim for apresentado, trate como evento suspeito e revogue toda a família (family_id) do usuário (ou ao menos invalide a sessão/token chain).
// Pseudo fluxo de rotação refresh(oldToken): 1) row = findByHash(hash(oldToken)) 2) if !row or row.expires_at < now: 401 3) if row.revoked_at != null: revokeFamily(row.family_id); return 403 4) newToken = randomString(64) 5) insert new row with same family_id, token_hash=hash(newToken) 6) update old row set revoked_at=now, replaced_by=newId 7) return accessToken + set-cookie refresh=newTokenPasso 7: logout (revogação)
No logout, revogue o refresh token atual (ou toda a família, se quiser logout global). Se estiver em cookie, limpe o cookie.
// POST /auth/logout -> 204 // Ação: marcar refresh token como revoked_at=nowPasso 8: autorização RBAC (middlewares/guards)
Crie um middleware requireRoles(...roles) para proteger endpoints. Ele deve:
- Exigir autenticação (middleware anterior)
- Verificar interseção entre roles do usuário e roles exigidos
- Responder 403 se não autorizado
// Exemplo de uso routes.post("/documents", auth, requireRoles("editor","admin"), createDocument)Passo 9: autorização ABAC (regra por atributo do recurso)
Implemente a regra ABAC no update de documento. Evite confiar no client para informar owner; busque o documento no banco e compare.
// PATCH /documents/:id // Regra: admin pode; editor só se doc.owner_id == req.auth.userId service.updateDocument(id, input, auth): doc = repo.findById(id) if !doc: throw 404 if hasRole(auth, "admin"): return repo.update(...) if hasRole(auth, "editor") && doc.owner_id == auth.userId: return repo.update(...) throw 403Passo 10: rate limiting e proteção contra abuso
Aplique rate limiting com granularidade por rota:
/auth/login: mais restrito (ex.: 5–10 tentativas/min por IP + por email)/auth/refresh: moderado (ex.: 30/min)- Demais rotas: limites razoáveis por IP/usuário
Retorne 429 com cabeçalhos de rate limit (quando suportado) e mensagem padronizada.
{ "error": { "code": "RATE_LIMITED", "message": "Muitas requisições, tente novamente em instantes" } }Passo 11: CSRF/XSS (quando aplicável)
Se o refresh token estiver em cookie e o endpoint /auth/refresh aceitar credenciais automaticamente, proteja contra CSRF:
- Use
SameSite=LaxouStrictquando possível - Para cenários cross-site, implemente token anti-CSRF (double submit cookie ou header custom) e valide no servidor
- Exija
Content-Type: application/jsone recuse requests sem header esperado
Para XSS (principalmente se houver front-end), evite armazenar access token em local acessível por JS quando possível; prefira mantê-lo em memória e renovar via refresh. No back-end, garanta que respostas não reflitam entradas sem sanitização quando renderização HTML existir (em API pura, o risco é menor, mas logs e mensagens de erro ainda devem ser cuidadosos).
Passo 12: política de senha (aplicação no cadastro e troca)
Aplique a política definida no projeto (ex.: mínimo 12 caracteres, bloquear senhas comuns, limitar tamanho máximo, impedir senha igual ao email). Garanta que erros de validação retornem 400 com detalhes por campo.
// Exemplo de validação adicional if isCommonPassword(password): 400 if password contains emailLocalPart: 400Documentação dos endpoints (modelo obrigatório)
Documente cada endpoint com: autenticação requerida, roles, input, output e erros. Você pode usar OpenAPI, mas aqui vai um modelo textual mínimo que deve existir no repositório (ex.: docs/api.md).
Exemplo: POST /auth/login
- Auth: não
- Rate limit: 10/min por IP e 5/min por email
- Input:
{ "email": "user@example.com", "password": "string" }- Output 200:
{ "accessToken": "...", "tokenType": "Bearer", "expiresIn": 900 }- Erros:
- 400 VALIDATION_ERROR
- 401 INVALID_CREDENTIALS
- 423 USER_DISABLED
- 429 RATE_LIMITED
Exemplo: PATCH /documents/:id
- Auth: sim (Bearer)
- RBAC: editor ou admin
- ABAC: se editor, deve ser owner
- Input:
{ "title": "string?", "content": "string?", "visibility": "PRIVATE|ORG|PUBLIC?" }- Output 200:
{ "id": "uuid", "ownerId": "uuid", "title": "...", "content": "...", "visibility": "PRIVATE" }- Erros:
- 401 UNAUTHORIZED
- 403 FORBIDDEN
- 404 NOT_FOUND
- 400 VALIDATION_ERROR
Checklist de segurança (entregável obrigatório)
Inclua um arquivo SECURITY_CHECKLIST.md com itens marcáveis. Exemplo:
- [ ] Senhas armazenadas com hash forte (bcrypt/argon2) e parâmetros adequados
- [ ] Validação de input em todas as rotas de escrita
- [ ] Mensagens de erro não vazam se usuário existe (quando aplicável)
- [ ] Access token curto e refresh token rotacionado
- [ ] Refresh token armazenado como hash no banco
- [ ] Reutilização de refresh token detectada e tratada (revogação por família)
- [ ] Logout revoga refresh token (e limpa cookie, se usado)
- [ ] Rate limiting aplicado em login/refresh e rotas sensíveis
- [ ] CSRF mitigado no refresh/logout se usar cookies
- [ ] Cabeçalhos de segurança básicos configurados (ex.: no proxy/app)
- [ ] Logs não registram tokens nem senhas
- [ ] RBAC aplicado em rotas administrativas
- [ ] ABAC implementado e testado (owner check)
Suíte mínima de testes (entregável obrigatório)
Crie testes de integração cobrindo fluxos e erros. Abaixo está um conjunto mínimo recomendado (ex.: Jest + Supertest, pytest, etc.).
Autenticação
- Cadastro: 201; 409 email duplicado; 400 senha fraca
- Login: 200; 401 senha errada; 423 usuário desabilitado; 429 após exceder limite
- Access token: 401 sem token; 401 token inválido; 401 token expirado (simulado)
Refresh/rotação
- Refresh válido: retorna novo access e novo refresh; antigo fica revogado e com replaced_by
- Refresh expirado: 401
- Reutilização do refresh antigo: 403 e família revogada (ou política definida)
- Logout: 204 e refresh revogado; refresh após logout: 401/403 conforme política
Autorização RBAC/ABAC
- Viewer não cria documento: 403
- Editor cria documento: 201
- Editor atualiza próprio documento: 200
- Editor tenta atualizar documento de outro: 403 (ABAC)
- Admin atualiza documento de outro: 200
- Admin deleta documento: 204; editor deleta: 403
Exemplo de caso de teste (pseudo)
it("editor não pode editar documento de outro usuário", async () => { const editor = await registerAndLogin("e@x.com", "StrongPass...", ["editor"]) const other = await registerAndLogin("o@x.com", "StrongPass...", ["editor"]) const doc = await createDocument(other.accessToken, { title:"A", content:"B" }) const res = await request(app) .patch(`/documents/${doc.id}`) .set("Authorization", `Bearer ${editor.accessToken}`) .send({ title: "hack" }) expect(res.status).toBe(403) expect(res.body.error.code).toBe("FORBIDDEN")})Critérios claros de entrega (o que será avaliado)
Fluxo funcional
- Cadastro e login funcionando com validações
- Access token protege endpoints
- Refresh token renova sessão com rotação
- Logout revoga corretamente
Cobertura de casos de erro
- Erros 400/401/403/404/409/423/429 implementados onde aplicável
- Formato de erro padronizado
- Mensagens seguras (sem vazar detalhes sensíveis)
Validações corretas e segurança
- Rate limiting ativo em rotas sensíveis
- CSRF mitigado se refresh/logout usam cookie
- RBAC aplicado em rotas protegidas
- ABAC aplicado no recurso escolhido (owner check) e testado
- Checklist de segurança preenchido
Documentação e testes
- Documentação de endpoints com inputs/outputs e códigos de erro
- Suíte mínima de testes passando (incluindo refresh/rotação e ABAC)
- Instruções de execução (ex.:
READMEcom setup, variáveis de ambiente e comandos)