Capítulo 10: OAuth2 com Password Flow e Geração de Tokens JWT
Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente envia usuário/senha"] --> B["POST /auth/token (OAuth2PasswordRequestForm)"]
B --> C["Valida credenciais no banco"]
C -->|"ok"| D["jwt.encode(header.payload.signature)"]
D --> E["access_token + token_type=bearer"]
C -->|"falha"| F["401 Unauthorized"]
G["Requisição protegida"] --> H["Authorization: Bearer <token>"]
H --> I["OAuth2PasswordBearer extrai o token"]
I --> J["jwt.decode() valida assinatura e exp"]
J -->|"válido"| K["Depends(get_current_user)"]
J -->|"expirado/inválido"| L["401 Unauthorized"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style D fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style E fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style F fill:#ffebee,stroke:#e53935,stroke-width:2px
style J fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style K fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style L fill:#ffebee,stroke:#e53935,stroke-width:2px
🏛️ 1. OAuth2 Password Flow e JWT: autenticação sem estado no servidor
OAuth2 é uma especificação de autorização, não de autenticação — mas o FastAPI a utiliza como esqueleto padronizado para descrever “como o cliente envia credenciais e recebe um token” de um jeito que o Swagger UI entende nativamente (o botão “Authorize” na documentação interativa vem daqui). O fluxo relevante para APIs internas é o Resource Owner Password Credentials (Password Flow): o cliente envia usuário e senha diretamente ao backend, que devolve um token de acesso.
OAuth2PasswordRequestForm: o contrato do login
O FastAPI já fornece a dependência OAuth2PasswordRequestForm, que espera os dados como application/x-www-form-urlencoded (não JSON) com os campos username e password — mantendo compatibilidade com clientes OAuth2 padrão, incluindo o próprio Swagger UI:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
app = FastAPI()
@app.post("/auth/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
usuario = autenticar_usuario(form_data.username, form_data.password)
if not usuario:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Usuário ou senha incorretos",
headers={"WWW-Authenticate": "Bearer"},
)
token = criar_access_token({"sub": usuario.email})
return {"access_token": token, "token_type": "bearer"}
Anatomia de um JWT
Um JSON Web Token é composto por três segmentos codificados em Base64URL e separados por pontos: header.payload.signature. O header declara o algoritmo (HS256, RS256); o payload carrega as claims — dados sobre o usuário e metadados do token; a signature garante que o payload não foi adulterado. É importante entender que o payload não é criptografado, apenas assinado — qualquer um pode decodificar e ler o conteúdo (em jwt.io, por exemplo). Por isso, dados sensíveis (senha, número de cartão) nunca devem ir dentro de um JWT.
As claims padrão mais usadas: sub (subject — identidade do usuário), exp (expiration — timestamp Unix após o qual o token é rejeitado), iat (issued at — quando foi emitido) e iss (issuer — quem emitiu).
import jwt
from datetime import datetime, timedelta, timezone
SECRET_KEY = "chave-secreta-de-no-minimo-32-bytes-em-producao"
ALGORITHM = "HS256"
def criar_access_token(dados: dict, expira_em: timedelta = timedelta(minutes=15)) -> str:
payload = dados.copy()
agora = datetime.now(timezone.utc)
payload.update({"iat": agora, "exp": agora + expira_em})
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def decodificar_token(token: str) -> dict:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
OAuth2PasswordBearer: extraindo e validando o token em cada rota
OAuth2PasswordBearer(tokenUrl="auth/token") é uma dependência que sabe procurar o header Authorization: Bearer <token>, extrair apenas o token e injetá-lo em qualquer rota — além de documentar automaticamente no OpenAPI qual endpoint emite o token:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credenciais_invalidas = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Não foi possível validar as credenciais",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = decodificar_token(token)
email: str | None = payload.get("sub")
if email is None:
raise credenciais_invalidas
except jwt.ExpiredSignatureError:
raise credenciais_invalidas
except jwt.InvalidTokenError:
raise credenciais_invalidas
return buscar_usuario_por_email(email)
Simétrico (HS256) vs assimétrico (RS256)
Com HS256, a mesma chave secreta assina e valida o token — simples, mas exige que todo serviço que precisa validar o token conheça o segredo, o que é arriscado em arquiteturas de microsserviços. Com RS256, a assinatura usa um par de chaves RSA: o serviço de autenticação assina com a chave privada, e qualquer outro serviço (gateway, microsserviço consumidor) valida com a chave pública, sem nunca ter acesso ao segredo que permitiria forjar tokens. Essa é a escolha padrão quando múltiplos serviços independentes precisam verificar tokens emitidos por uma autoridade central.
Access token curto + refresh token longo
Como o JWT é stateless, revogar um token antes do exp não é trivial (o servidor não guarda estado de sessão para invalidar). A mitigação padrão é usar dois tokens: um access_token de vida curta (5-15 min), usado nas requisições, e um refresh_token de vida longa, usado apenas para obter um novo access_token quando o atual expira. O refresh_token fica sujeito a uma blacklist (em Redis, por exemplo) que permite revogação real em caso de logout ou comprometimento — o custo de consultar essa blacklist é pago só ocasionalmente (na renovação), não em cada requisição autenticada.