🔒
CORS, CSRF, cookies seguros: segurança web fundamental
⏱ 14 min de leitura·+70 XP
CORS, CSRF e configuração de cookies são as defesas de segurança que mais desenvolvedores configuram errado — seja abrindo demais (CORS: *) ou esquecendo atributos críticos em cookies de sessão. Entender o modelo de ameaça por trás de cada um evita configurações de boa vontade mas inseguras.
Same-Origin Policy e CORS
# Origin = protocolo + host + porta
# https://app.example.com:443 ← origem completa
# http://app.example.com:80 ← origem DIFERENTE (http vs https)
# https://api.example.com:443 ← origem DIFERENTE (host diferente)
# https://app.example.com:8443 ← origem DIFERENTE (porta diferente)
# Preflight (OPTIONS) é enviado para requests "não simples":
# Não-simples: método != GET/POST/HEAD, ou headers customizados, ou Content-Type != form/text
# Exemplo de preflight:
# OPTIONS /api/users HTTP/1.1
# Origin: https://app.example.com
# Access-Control-Request-Method: POST
# Access-Control-Request-Headers: Content-Type, Authorization
# Resposta do servidor:
# HTTP/1.1 200 OK
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Max-Age: 86400 ← cacheia o preflight por 24h
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS middleware — configuração CORRETA para produção:
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://app.example.com",
"https://admin.example.com",
], # NUNCA "*" se usar credentials
allow_credentials=True, # permite cookies cross-origin
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Request-ID"],
max_age=86400, # cache do preflight
)
# Armadilha: allow_origins=["*"] + allow_credentials=True = INVÁLIDO
# Browser recusa se origin for * e credentials estiver true
# Deve ser origem específica quando credentials=True
# CORS NÃO protege de CSRF!
# CORS bloqueia LEITURA da resposta, não o ENVIO da request
# Um form HTML pode fazer POST cross-origin sem CORS (é "simples")
# → CSRF protection é separada de CORSCSRF: ataques e defesas
# Cenário de ataque CSRF:
# 1. Vítima logada em bank.com (cookie de sessão ativo)
# 2. Vítima visita evil.com
# 3. evil.com tem: <form action="https://bank.com/transfer" method="POST">
# <input name="to" value="hacker_account">
# <input name="amount" value="10000">
# </form>
# <script>document.forms[0].submit();</script>
# 4. Browser envia o POST para bank.com COM O COOKIE DA VÍTIMA
# 5. bank.com processa a transferência como se fosse a vítima
# Defesa 1: SameSite Cookie (recomendado, mais simples)
# SameSite=Strict: cookie nunca enviado cross-site (nem em links externos)
# SameSite=Lax: cookie enviado em navegação top-level GET (links externos)
# MAS não em POST/PUT/DELETE cross-site → mitiga CSRF
# SameSite=None: enviado sempre (deve ter Secure obrigatoriamente)
from fastapi.responses import Response
import secrets
def set_session_cookie(response: Response, session_id: str):
"""Cookie de sessão com todos os atributos de segurança."""
response.set_cookie(
key="session_id",
value=session_id,
httponly=True, # JS não acessa
secure=True, # apenas HTTPS
samesite="lax", # mitiga CSRF, permite OAuth flows
max_age=86400, # 24h
path="/", # todo o site
# domain=".example.com", # subdomínios (use com cuidado)
)
# Defesa 2: CSRF Token (necessário quando SameSite não é suficiente)
from fastapi import Cookie, Form, HTTPException
def generate_csrf_token() -> str:
return secrets.token_urlsafe(32)
@app.post("/transfer")
async def transfer(
to: str = Form(...),
amount: float = Form(...),
csrf_token: str = Form(...), # token no body do form
session_csrf: str = Cookie(None), # mesmo token no cookie
):
# Valida que o token do form = token do cookie:
if not secrets.compare_digest(csrf_token, session_csrf):
raise HTTPException(status_code=403, detail="CSRF token inválido")
# Processa transferência...
# Defesa 3: Custom header (para SPAs com JSON API)
# Browser impede JS de adicionar headers customizados em requests cross-origin
# sem aprovação CORS — então se a API exige X-Requested-With, CSRF é impossível
@app.post("/api/transfer")
async def api_transfer(request: Request, ...):
if not request.headers.get("X-Requested-With"):
raise HTTPException(403, "Missing X-Requested-With header")
# Não precisa de CSRF token — custom header é proteção suficiente para JSON APIs✅
Modelo mental: Same-Origin Policy protege de leitura cross-origin. CORS relaxa o SOP de forma controlada — nunca use *, apenas origens explícitas, especialmente com credentials. CSRF explora que browsers enviam cookies automaticamente em requests cross-site — mitigado com SameSite=Lax (default moderno) ou CSRF tokens. HttpOnly bloqueia roubo de cookie por XSS. Secure garante transmissão apenas em HTTPS. Use sempre os quatro juntos para cookies de sessão: HttpOnly + Secure + SameSite=Lax + Max-Age.
💡
Trilha concluída! Próxima trilha: OSI e TCP/IP — volte ao início para revisar as camadas de rede.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo