Solução 09 - Segurança e Autenticação com JWT 🔐
Navegação
← Exercício 09 | Próxima Solução →
🟢 Respostas Fáceis
1. Autenticação vs Autorização
Resposta 1
Diferença entre Autenticação e Autorização:
| Aspecto | **Autenticação** | **Autorização** |
|---------|------------------|------------------|
| **Pergunta** | **"Quem é você?"** | **"O que você pode fazer?"** |
| **Processo** | **Verifica identidade** | **Verifica permissões** |
| **Quando** | **Login** (entrada no sistema) | **A cada ação** dentro do sistema |
| **Como** | Login/senha, biometria, 2FA | Roles, permissions, policies |
**🏠 Analogia da Casa:**
- **Autenticação**: Mostrar **RG na portaria** para provar quem você é
- **Autorização**: **Chave específica** que define quais quartos você pode entrar
**🔄 Fluxo Completo:**
```mermaid
sequenceDiagram
participant U as Usuário
participant A as Autenticação
participant S as Sistema
participant B as Autorização
U->>+A: Login (email + senha)
A->>A: Validar credenciais
A-->>-U: ✅ Token JWT (prova de identidade)
U->>+S: Acessar /admin/users (com token)
S->>+B: Verificar permissões
B->>B: User é ADMIN?
B-->>-S: ✅ Autorizado / ❌ Negado
S-->>-U: Conteúdo / Erro 403
```
2. Partes do JWT
Resposta 2
3 Partes de um token JWT:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│ │ │
│ HEADER │ PAYLOAD │ SIGNATURE
```
**📋 Detalhamento das Partes:**
| Parte | Conteúdo | Codificação | Finalidade |
|-------|----------|-------------|------------|
| **Header** | Tipo do token e algoritmo | Base64 | **Metadados** do token |
| **Payload** | Claims (dados do usuário) | Base64 | **Informações** transportadas |
| **Signature** | Assinatura criptográfica | HMACSHA256 | **Integridade** do token |
**🔍 Exemplo Decodificado:**
```json
// HEADER
{
"alg": "HS256",
"typ": "JWT"
}
// PAYLOAD
{
"sub": "1234567890",
"name": "João Silva",
"role": "admin",
"iat": 1516239022,
"exp": 1516325422
}
// SIGNATURE (não decodificável sem chave)
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
```
🟡 Respostas Médias
3. Informações Sensíveis no JWT
Resposta 3
Por que NÃO incluir informações sensíveis no Payload:
**🚨 Problemas de Segurança:**
- **Base64 ≠ Criptografia**: Qualquer um pode decodificar o payload
- **Token viaja**: Headers HTTP, logs de servidor, cache de browser
- **Immutable**: Não dá para "apagar" token já enviado
- **Debugging**: Tokens aparecem em ferramentas de desenvolvimento
**❌ O que NUNCA colocar:**
```json
{
"userId": 123,
"name": "João",
"senha": "senha123", ❌ NUNCA!
"cpf": "123.456.789-00", ❌ PII sensível
"numeroCartao": "1234567890", ❌ Dados financeiros
"salario": 15000, ❌ Info confidencial
"endereco": "Rua X, 123" ❌ Dados pessoais
}
```
**✅ O que é SEGURO colocar:**
```json
{
"sub": "user_123", ✅ ID não sensível
"name": "João Silva", ✅ Nome público
"role": "admin", ✅ Permissões
"email": "joao@email.com", ✅ Se necessário
"iat": 1516239022, ✅ Timestamps
"exp": 1516325422, ✅ Expiração
"permissions": ["read", "write"] ✅ Autorizações
}
```
**🔒 Demonstração de Vulnerabilidade:**
```javascript
// QUALQUER PESSOA pode fazer isso:
const token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...";
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload); // Vê TODOS os dados do payload!
```
4. Arquitetura Stateless para Escala
Resposta 4
Vantagens Stateless para Milhões de Usuários:
**🏗️ Stateful vs Stateless:**
```mermaid
graph TD
A[Cliente] --> B[Load Balancer]
subgraph "❌ STATEFUL"
B --> C[Servidor A]
B --> D[Servidor B]
C --> E[Session Store]
D --> E
E --> F[Redis/Database]
end
subgraph "✅ STATELESS"
B --> G[Servidor A]
B --> H[Servidor B]
G --> I[JWT Token]
H --> J[JWT Token]
end
style E fill:#ff6b6b
style F fill:#ff6b6b
style I fill:#81c784
style J fill:#81c784
```
**📊 Benefícios Stateless:**
| Aspecto | Stateful (Sessions) | Stateless (JWT) |
|---------|---------------------|-----------------|
| **Escalabilidade** | Limitada pelo session store | **Infinita** (horizontal) |
| **Performance** | Consulta BD a cada request | **Zero consultas** extras |
| **Complexidade** | Session clustering, sticky sessions | **Distribuição simples** |
| **Falhas** | Server down = session perdida | **Fault tolerant** |
| **CDN/Cache** | Difícil de cachear | **Cache friendly** |
**⚡ Cenário Real de Escala:**
```javascript
// Stateful - Problema
// 10 milhões de usuários online = 10M sessions em memória
// Cada servidor precisa de 100GB+ RAM apenas para sessions
// Load balancer precisa de "sticky sessions" complicadas
// Stateless - Solução
app.get('/dashboard', verificarJWT, (req, res) => {
// Token já contém tudo que precisamos
const { userId, role, permissions } = req.user;
// Sem consulta ao session store!
if (permissions.includes('dashboard.read')) {
return res.json({ dashboard: "data" });
}
return res.status(403).json({ erro: "Sem permissão" });
});
```
**🚀 Impacto na Performance:**
```
Stateful: Request → Load Balancer → Server → Session Store → Business Logic
⏱️ ~50ms ⏱️ ~10ms ⏱️ ~30ms ⏱️ ~20ms
Stateless: Request → Load Balancer → Server → Business Logic
⏱️ ~50ms ⏱️ ~10ms ⏱️ ~20ms
Ganho: 40%+ mais rápido + infinitamente escalável
```
🔴 Resposta Desafio
5. Análise de Token JWT
Resposta 5
Cenário: Token JWT Interceptado
**a) Lendo dados sem chave secreta:**
```javascript
// ✅ POSSÍVEL - Payload não é criptografado!
const tokenInterceptado = "eyJhbGci...";
function lerPayloadSemChave(token) {
// Split nas 3 partes
const partes = token.split('.');
const payload = partes[1];
// Decode Base64
const dadosDecodificados = JSON.parse(atob(payload));
console.log("Nome do usuário:", dadosDecodificados.name);
console.log("Role:", dadosDecodificados.role);
console.log("ID:", dadosDecodificados.sub);
return dadosDecodificados;
}
// Exemplo real:
lerPayloadSemChave("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
// Output: { sub: "1234567890", name: "John Doe", iat: 1516239022 }
```
**b) Por que servidor rejeita token alterado:**
```javascript
// Atacante tenta burlar:
const payloadOriginal = { "sub": "123", "name": "User", "role": "user" };
const payloadMalicioso = { "sub": "1", "name": "Admin", "role": "admin" };
// ❌ Token alterado será REJEITADO porque:
function verificarToken(token, secret) {
const [header, payload, signature] = token.split('.');
// 1. Server recria a assinatura
const assinaturaEsperada = crypto
.createHmac('sha256', secret)
.update(header + '.' + payload)
.digest('base64url');
// 2. Compara com assinatura do token
if (signature !== assinaturaEsperada) {
throw new Error("Token inválido - foi alterado!");
}
return JSON.parse(atob(payload));
}
```
**🔐 Fluxo da Verificação:**
```mermaid
sequenceDiagram
participant A as Atacante
participant S as Servidor
A->>A: Intercepta token válido
A->>A: Altera payload (muda user_id)
A->>+S: Envia token modificado
S->>S: Extrai header + payload alterado
S->>S: Recalcula signature com SECRET
S->>S: Compara com signature do token
Note over S: ❌ Signatures não batem!
S-->>-A: 401 Unauthorized
Note over A,S: Token foi rejeitado!
```
**c) Armazenamento seguro no Frontend:**
```javascript
// ❌ Opções INSEGURAS:
// localStorage - XSS pode roubar
// sessionStorage - XSS pode roubar
// cookies sem flags - CSRF e XSS
// ✅ Opção MAIS SEGURA:
// httpOnly + secure + sameSite cookies
// Backend seta cookie:
app.post('/login', (req, res) => {
const token = gerarJWT(usuario);
res.cookie('authToken', token, {
httpOnly: true, // ✅ JavaScript não consegue acessar
secure: true, // ✅ Apenas HTTPS
sameSite: 'strict', // ✅ Proteção CSRF
maxAge: 24 * 60 * 60 * 1000 // 24h
});
res.json({ sucesso: true });
});
// ✅ Alternativa para SPA:
// localStorage + XSS Protection
class TokenManager {
static salvar(token) {
// Validar que não estamos sendo atacados
if (this.detectarXSS()) {
console.error("Possível ataque XSS detectado!");
return;
}
localStorage.setItem('authToken', token);
}
static obter() {
return localStorage.getItem('authToken');
}
static limpar() {
localStorage.removeItem('authToken');
}
static detectarXSS() {
// Verificações básicas de segurança
return document.domain !== 'meusite.com' ||
window.location.protocol !== 'https:';
}
}
```
**🛡️ Implementação de Middleware Seguro:**
```javascript
function middlewareJWT(req, res, next) {
try {
// 1. Extrair token (cookie ou header)
const token = req.cookies.authToken ||
req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ erro: "Token não fornecido" });
}
// 2. Verificar integridade e validade
const payload = jwt.verify(token, process.env.JWT_SECRET);
// 3. Verificar se não expirou
if (payload.exp < Date.now() / 1000) {
return res.status(401).json({ erro: "Token expirado" });
}
// 4. Adicionar dados do usuário na request
req.user = payload;
next();
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
erro: "Token inválido",
detalhes: "Token foi alterado ou corrompido"
});
}
return res.status(500).json({ erro: "Erro interno" });
}
}
```
**📱 Proteção Adicional - Refresh Token:**
```javascript
// Sistema mais seguro: Access Token (curto) + Refresh Token (longo)
app.post('/login', async (req, res) => {
const usuario = await validarCredenciais(req.body);
const accessToken = jwt.sign(
{ userId: usuario.id, role: usuario.role },
process.env.ACCESS_SECRET,
{ expiresIn: '15m' } // Token curto
);
const refreshToken = jwt.sign(
{ userId: usuario.id },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' } // Token longo
);
// Salvar refresh token no BD (pode ser revogado)
await salvarRefreshToken(usuario.id, refreshToken);
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
path: '/auth/refresh' // Apenas endpoint específico
});
res.json({ accessToken, expiresIn: 15 * 60 });
});
```
!!! tip "Dicas para Próximos Estudos" - Implemente refresh tokens para tokens de vida curta - Configure JWT blacklist para logout forçado - Use asymmetric keys (RS256) em microservices - Implement rate limiting no endpoint de login
Navegação