<h2>Por que Autenticação é Crítica em APIs Modernas</h2>
<p>Quando você constrói uma API, está criando um ponto de acesso a dados ou funcionalidades que podem ser sensíveis. Sem autenticação adequada, qualquer pessoa poderia acessar informações de outros usuários, modificar dados críticos ou executar ações não autorizadas. A autenticação resolve o problema fundamental: "quem você é?". A autorização — que vem depois — responde "o que você pode fazer?".</p>
<p>Em FastAPI, a segurança não é um add-on bolado ao final do projeto. É um pilar arquitetural que deve estar presente desde o início. O framework oferece ferramentas nativas para implementar padrões modernos como JWT (JSON Web Tokens) e OAuth2, reduzindo drasticamente o risco de vulnerabilidades comuns em APIs REST.</p>
<h2>JWT e OAuth2: Os Pilares da Autenticação Moderna</h2>
<h3>O que é JWT (JSON Web Token)?</h3>
<p>Um JWT é um padrão aberto (RFC 7519) que define uma forma compacta e segura de transmitir informações entre partes. Ele é composto por três partes separadas por pontos: <strong>header</strong>, <strong>payload</strong> e <strong>signature</strong>. O header define o tipo de token e o algoritmo de criptografia. O payload contém as claims (informações) que você quer transmitir, como o ID do usuário ou seu email. A signature garante que o token não foi alterado — qualquer mudança no header ou payload invalidaria a signature.</p>
<p>O grande diferencial do JWT é que ele é <strong>stateless</strong>. Diferente de sessões tradicionais, o servidor não precisa armazenar informações sobre cada token emitido. Quando o cliente envia um JWT, o servidor valida a signature e confia que as informações dentro do token são legítimas. Isso torna aplicações distribuídas muito mais escaláveis.</p>
<h3>O que é OAuth2?</h3>
<p>OAuth2 é um protocolo de <strong>autorização</strong> que define como terceiras partes podem obter acesso limitado aos recursos de um usuário sem conhecer sua senha. Ele estabelece fluxos padronizados para diferentes cenários: aplicações web, aplicações mobile, serviços backend-to-backend, etc. O fluxo mais comum em APIs é o <strong>Password Flow</strong> (ou Resource Owner Password Credentials), onde o cliente envia diretamente username e password para receber um token.</p>
<p>Em FastAPI, combinamos JWT com OAuth2: usamos OAuth2 para o fluxo de autenticação (receber credenciais e emitir um token) e JWT como o formato do token que trafega entre cliente e servidor.</p>
<h2>Implementação Prática: Autenticação Completa com JWT e OAuth2</h2>
<h3>Estrutura Base e Dependências</h3>
<p>Começamos importando as ferramentas necessárias do FastAPI e bibliotecas externas:</p>
<pre><code class="language-python">from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone
from typing import Optional
import jwt
import os
app = FastAPI(title="API Segura com JWT")
Configurações
SECRET_KEY = os.getenv("SECRET_KEY", "sua-chave-super-secreta-aqui")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
Contexto para hash de senhas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
Scheme OAuth2
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")</code></pre>
<blockquote><p><strong>Importante</strong>: Nunca deixe a SECRET_KEY hardcoded em produção. Use variáveis de ambiente. A SECRET_KEY deve ser uma string longa e aleatória — quanto mais complexa, melhor.</p></blockquote>
<h3>Modelos Pydantic</h3>
<p>Define os modelos que representam usuários e tokens:</p>
<pre><code class="language-python">class Usuario(BaseModel):
id: int
username: str
email: EmailStr
full_name: Optional[str] = None
disabled: Optional[bool] = False
class UsuarioInDB(Usuario):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
expires_in: int
class TokenData(BaseModel):
username: Optional[str] = None</code></pre>
<h3>Funções Auxiliares</h3>
<p>Implementamos funções para hash de senhas, validação e criação de tokens:</p>
<pre><code class="language-python">def hash_password(password: str) -> str:
"""Gera hash bcrypt da senha"""
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verifica se a senha corresponde ao hash"""
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""Cria um JWT assinado"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> dict:
"""Decodifica e valida um JWT"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido"
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expirado"
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido"
)</code></pre>
<h3>Banco de Dados Mock</h3>
<p>Para fins didáticos, usamos um dicionário em memória. Em produção, seria um banco de dados real:</p>
<pre><code class="language-python"># Simulando um banco de dados
fake_users_db = {
"joao": {
"id": 1,
"username": "joao",
"email": "joao@example.com",
"full_name": "João Silva",
"hashed_password": hash_password("senha123"),
"disabled": False,
},
"maria": {
"id": 2,
"username": "maria",
"email": "maria@example.com",
"full_name": "Maria Santos",
"hashed_password": hash_password("outrasenha456"),
"disabled": False,
}
}
def get_user(username: str) -> Optional[UsuarioInDB]:
"""Busca usuário no 'banco de dados'"""
if username in fake_users_db:
user_dict = fake_users_db[username]
return UsuarioInDB(**user_dict)
return None
def authenticate_user(username: str, password: str) -> Optional[UsuarioInDB]:
"""Valida credenciais do usuário"""
user = get_user(username)
if not user:
return None
if not verify_password(password, user.hashed_password):
return None
return user</code></pre>
<h3>Dependências e Endpoints Protegidos</h3>
<p>Aqui criamos a dependência que valida o token e protege os endpoints:</p>
<pre><code class="language-python">async def get_current_user(token: str = Depends(oauth2_scheme)) -> UsuarioInDB:
"""Dependência que valida o token e retorna o usuário atual"""
payload = decode_token(token)
username: str = payload.get("sub")
user = get_user(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Usuário não encontrado"
)
return user
async def get_current_active_user(
current_user: UsuarioInDB = Depends(get_current_user)
) -> UsuarioInDB:
"""Valida se o usuário está ativo"""
if current_user.disabled:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Usuário desativado"
)
return current_user
Endpoint para login
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
"""Autentica o usuário e retorna um JWT"""
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Username ou senha incorretos",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires
)
return {
"access_token": access_token,
"token_type": "bearer",
"expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60
}
Endpoint protegido
@app.get("/usuarios/me", response_model=Usuario)
async def read_users_me(current_user: UsuarioInDB = Depends(get_current_active_user)):
"""Retorna informações do usuário autenticado"""
return current_user
Outro endpoint protegido
@app.get("/dados-sensiveis")
async def dados_sensiveis(current_user: UsuarioInDB = Depends(get_current_active_user)):
"""Apenas usuários autenticados acessam"""
return {
"message": f"Olá {current_user.full_name}!",
"data": "Informação confidencial apenas para você"
}</code></pre>
<h2>Segurança de Endpoints: Validação, Rate Limiting e Boas Práticas</h2>
<h3>Validação de Entrada e HTTPS</h3>
<p>Toda entrada de dados deve ser validada. Pydantic já faz grande parte do trabalho, mas você pode adicionar validadores customizados:</p>
<pre><code class="language-python">from pydantic import validator
class UsuarioRegistro(BaseModel):
username: str
email: EmailStr
password: str
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'username deve conter apenas letras e números'
assert len(v) >= 3, 'username deve ter pelo menos 3 caracteres'
return v
@validator('password')
def password_strength(cls, v):
if len(v) < 8:
raise ValueError('Senha deve ter no mínimo 8 caracteres')
if not any(char.isupper() for char in v):
raise ValueError('Senha deve conter pelo menos uma letra maiúscula')
return v</code></pre>
<p>Em produção, <strong>sempre use HTTPS</strong>. O HTTP expõe tokens em texto plano. Com HTTPS, a comunicação é criptografada, protegendo JWTs em trânsito.</p>
<h3>Refresh Tokens e Expiração</h3>
<p>Tokens com expiração curta (15-30 minutos) são mais seguros. Para manter a sessão viva sem pedir as credenciais novamente, use refresh tokens:</p>
<pre><code class="language-python">def create_tokens(username: str) -> dict:
"""Cria access_token e refresh_token"""
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": username, "type": "access"},
expires_delta=access_token_expires
)
refresh_token_expires = timedelta(days=7)
refresh_token = create_access_token(
data={"sub": username, "type": "refresh"},
expires_delta=refresh_token_expires
)
return {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}
@app.post("/refresh")
async def refresh_access_token(refresh_token: str):
"""Emite um novo access_token usando o refresh_token"""
payload = decode_token(refresh_token)
if payload.get("type") != "refresh":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido"
)
username = payload.get("sub")
new_access_token = create_access_token(
data={"sub": username, "type": "access"},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
return {"access_token": new_access_token, "token_type": "bearer"}</code></pre>
<h3>Rate Limiting</h3>
<p>Proteja seus endpoints contra ataques de força bruta usando rate limiting:</p>
<pre><code class="language-python">from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.post("/token")
@limiter.limit("5/minute")
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
"""Máximo 5 tentativas por minuto"""
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
... resto do código</code></pre>
<h3>CORS (Cross-Origin Resource Sharing)</h3>
<p>Configure CORS apropriadamente para aceitar requisições apenas de origens confiáveis:</p>
<pre><code class="language-python">from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://seu-frontend.com"], # Específico em produção
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)</code></pre>
<h2>Testando a Segurança</h2>
<p>Escreva testes para garantir que sua autenticação funciona corretamente:</p>
<pre><code class="language-python">from fastapi.testclient import TestClient
client = TestClient(app)
def test_login_sucesso():
"""Testa login com credenciais válidas"""
response = client.post(
"/token",
data={"username": "joao", "password": "senha123"}
)
assert response.status_code == 200
assert "access_token" in response.json()
assert response.json()["token_type"] == "bearer"
def test_login_falha():
"""Testa login com senha incorreta"""
response = client.post(
"/token",
data={"username": "joao", "password": "senhaerrada"}
)
assert response.status_code == 401
def test_endpoint_protegido_sem_token():
"""Testa acesso a endpoint protegido sem token"""
response = client.get("/usuarios/me")
assert response.status_code == 403
def test_endpoint_protegido_com_token():
"""Testa acesso a endpoint protegido com token válido"""
Primeiro faz login
login_response = client.post(
"/token",
data={"username": "joao", "password": "senha123"}
)
token = login_response.json()["access_token"]
Acessa endpoint protegido
response = client.get(
"/usuarios/me",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
assert response.json()["username"] == "joao"
def test_token_expirado():
"""Testa acesso com token expirado"""
Cria um token que expira em 1 segundo
token = create_access_token(
data={"sub": "joao"},
expires_delta=timedelta(seconds=1)
)
Aguarda a expiração
import time
time.sleep(2)
response = client.get(
"/usuarios/me",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 401</code></pre>
<h2>Conclusão</h2>
<p>Dominar autenticação em FastAPI significa entender três pontos essenciais: <strong>JWT é o formato de token stateless que torna APIs escaláveis</strong>, removendo a necessidade de armazenar sessões no servidor; <strong>OAuth2 é o protocolo que define fluxos seguros para autenticação</strong>, e em FastAPI você o implementa com <code>OAuth2PasswordBearer</code> e <code>OAuth2PasswordRequestForm</code>; e <strong>segurança vai além da autenticação</strong> — inclui validação de entrada, expiração de tokens, rate limiting, HTTPS e testes. Aplicar esses conceitos desde o início do projeto evita refatorações custosas e vulnerabilidades graves.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://fastapi.tiangolo.com/tutorial/security/" target="_blank" rel="noopener noreferrer">FastAPI Security Documentation</a></li>
<li><a href="https://jwt.io/introduction" target="_blank" rel="noopener noreferrer">JWT Introduction at jwt.io</a></li>
<li><a href="https://tools.ietf.org/html/rfc6749" target="_blank" rel="noopener noreferrer">RFC 6749 - The OAuth 2.0 Authorization Framework</a></li>
<li><a href="https://owasp.org/www-project-api-security/" target="_blank" rel="noopener noreferrer">OWASP API Security Top 10</a></li>
<li><a href="https://passlib.readthedocs.io/" target="_blank" rel="noopener noreferrer">Passlib Documentation</a></li>
</ul>
<p><!-- FIM --></p>