kb.erickguedes.com
APIs: Design, Arquitetura e Implementação

Autenticação e Autorização

Aula 3 de 7

Autenticação vs Autorização

Autenticação (AuthN): quem é você?
├── Login, JWT, OAuth, API Keys
├── HTTP headers: Authorization: Bearer <token>

Autorização (AuthZ): o que você pode fazer?
├── Permissões, roles, scopes
├── Baseada em: RBAC, ABAC, Claims

JWT — JSON Web Token

Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "123", "name": "João", "iat": 1710000000, "exp": 1710086400}
Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)

Token completo: header.payload.signature
Formato: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.xXxXxXxX
# Geração de JWT (Python)
import jwt
import datetime

payload = {
    "sub": "user_123",
    "name": "João Silva",
    "roles": ["admin", "operator"],
    "iat": datetime.datetime.utcnow(),
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}

token = jwt.encode(payload, "secret-key", algorithm="HS256")

Validação no Servidor

def verify_token(request):
    auth = request.headers.get("Authorization")
    if not auth or not auth.startswith("Bearer "):
        return 401

    token = auth.removeprefix("Bearer ")
    try:
        payload = jwt.decode(token, "secret-key", algorithms=["HS256"])
        request.user = payload
    except jwt.ExpiredSignatureError:
        return 401
    except jwt.InvalidTokenError:
        return 401

OAuth 2.0 — Delegação de Acesso

┌────────────┐     ┌────────────┐     ┌────────────┐
│  Cliente   │────▶│ Auth Server│────▶│  Resource  │
│  (App)     │◀────│ (Keycloak) │◀────│  (API)     │
└────────────┘     └────────────┘     └────────────┘
     │                   │
     │  1. Authorization │
     │  2. Code          │
     │  3. Code+Secret   │
     │  4. Access Token  │
     │  5. Token → API   │
# Authorization Code Flow (recomendado)
# 1. Redirect usuário para auth server
https://auth.empresa.com/auth?response_type=code&client_id=app&redirect_uri=https://app.com/callback

# 2. Receber code
GET /callback?code=abc123

# 3. Trocar code por token
curl -X POST https://auth.empresa.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=abc123&redirect_uri=https://app.com/callback&client_id=app&client_secret=secret"

# Response
{
  "access_token": "eyJhbG...",
  "refresh_token": "def456...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

API Keys

# Para machine-to-machine (M2M)
GET /api/v1/data
X-API-Key: abc-def-ghi-123

# Geração
import secrets
api_key = secrets.token_urlsafe(32)  # 43 chars

# Storage: hash da chave (bcrypt), não plaintext
# Rate limit por API Key

Rate Limiting

Headers de Rate Limit:
X-RateLimit-Limit: 100       ← max requests
X-RateLimit-Remaining: 87    ← remaining
X-RateLimit-Reset: 1710086400 ← reset timestamp

429 Too Many Requests
Retry-After: 30              ← segundos para esperar
# Rate limiting com Redis
import redis
import time

r = redis.Redis()

def check_rate_limit(client_id: str, max_requests: int = 100, window: int = 60):
    key = f"rate:{client_id}:{int(time.time() / window)}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, window)
    return count <= max_requests

Boas Práticas

✅ Segurança:
- JWT com exp curta (15 min), refresh token longa (7 dias)
- Sempre valide signature e exp
- Armazene tokens em httpOnly cookies (não localStorage)
- CSRF tokens para state-changing endpoints
- Rate limit por IP, API Key e User

✅ Senhas (se aplicável):
- Hash com bcrypt/argon2
- NUNCA armazene plaintext
- Mínimo 8 chars, sem limites absurdos
- Rate limit no login (5 tentativas/15 min)

JWT é stateless (não precisa de sessão no servidor). OAuth 2.0 é o padrão para delegação de acesso. Rate limiting protege contra abuso. Sempre use refresh tokens — não faça JWT com exp de dias.