Dominando Segurança em APIs em Projetos Reais Já leu

Autenticação e Autorização: O Primeiro Pilar A segurança de uma API começa com a identificação correta de quem está acessando seus recursos. Autenticação valida a identidade do usuário, enquanto autorização define o que esse usuário pode fazer. Em projetos reais, JWT (JSON Web Tokens) é o padrão de facto, especialmente em arquiteturas distribuídas. JWT funciona criando um token assinado que contém claims (dados) sobre o usuário. O servidor valida a assinatura sem precisar consultar um banco de dados a cada requisição. Implementar corretamente significa usar algoritmos robustos (RS256 ou ES256) e nunca deixar chaves secretas expostas. Para autorização granular, implemente roles e permissões no token. Nunca confie apenas no ID do usuário vindo da requisição; sempre valide contra o token decodificado. Proteção de Dados em Trânsito e Armazenamento Dados viajando pela rede devem ser criptografados. HTTPS é mandatório — não é negociável em produção. TLS 1.2 ou superior protege contra interceptação, mas você também precisa se preocupar com o que

Autenticação e Autorização: O Primeiro Pilar

A segurança de uma API começa com a identificação correta de quem está acessando seus recursos. Autenticação valida a identidade do usuário, enquanto autorização define o que esse usuário pode fazer. Em projetos reais, JWT (JSON Web Tokens) é o padrão de facto, especialmente em arquiteturas distribuídas.

JWT funciona criando um token assinado que contém claims (dados) sobre o usuário. O servidor valida a assinatura sem precisar consultar um banco de dados a cada requisição. Implementar corretamente significa usar algoritmos robustos (RS256 ou ES256) e nunca deixar chaves secretas expostas.

# Exemplo com Flask e PyJWT
from flask import Flask, request, jsonify
from functools import wraps
import jwt
from datetime import datetime, timedelta

app = Flask(__name__)
app.config['SECRET_KEY'] = 'sua-chave-super-secreta-aqui'

def token_obrigatorio(f):
    @wraps(f)
    def decorador(*args, **kwargs):
        token = request.headers.get('Authorization', '').replace('Bearer ', '')
        if not token:
            return jsonify({'erro': 'Token ausente'}), 401
        try:
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            request.usuario_id = payload['usuario_id']
        except jwt.InvalidTokenError:
            return jsonify({'erro': 'Token inválido'}), 401
        return f(*args, **kwargs)
    return decorador

@app.route('/login', methods=['POST'])
def login():
    dados = request.get_json()
    # Validar credenciais contra banco de dados
    usuario_id = 1  # Após validação real
    token = jwt.encode({
        'usuario_id': usuario_id,
        'exp': datetime.utcnow() + timedelta(hours=1)
    }, app.config['SECRET_KEY'], algorithm='HS256')
    return jsonify({'token': token})

@app.route('/dados-protegidos', methods=['GET'])
@token_obrigatorio
def dados_protegidos():
    return jsonify({'mensagem': f'Dados do usuário {request.usuario_id}'})

Para autorização granular, implemente roles e permissões no token. Nunca confie apenas no ID do usuário vindo da requisição; sempre valide contra o token decodificado.

Proteção de Dados em Trânsito e Armazenamento

Dados viajando pela rede devem ser criptografados. HTTPS é mandatório — não é negociável em produção. TLS 1.2 ou superior protege contra interceptação, mas você também precisa se preocupar com o que acontece quando os dados chegam ao servidor.

Senhas nunca devem ser armazenadas em texto plano. Use bcrypt, Argon2 ou PBKDF2. Para dados sensíveis (números de cartão, tokens), considere criptografia em repouso com AES-256. Implementar corretamente significa não reinventar a roda — use bibliotecas testadas.

# Exemplo com bcrypt para senhas
from bcrypt import hashpw, gensalt, checkpw
from cryptography.fernet import Fernet
import os

# Armazenar senha
def registrar_usuario(email, senha):
    salt = gensalt(rounds=12)
    senha_hash = hashpw(senha.encode(), salt)
    # Salvar no banco: INSERT INTO usuarios (email, senha) VALUES (email, senha_hash)
    return senha_hash

# Validar senha
def validar_login(email, senha):
    # senha_bd = SELECT senha FROM usuarios WHERE email = email
    return checkpw(senha.encode(), senha_bd)

# Criptografar dados sensíveis
def criptografar_cartao(numero_cartao):
    chave = os.getenv('ENCRYPTION_KEY')  # Armazenar em variável de ambiente
    cipher = Fernet(chave)
    cartao_criptografado = cipher.encrypt(numero_cartao.encode())
    return cartao_criptografado

def descriptografar_cartao(cartao_criptografado):
    chave = os.getenv('ENCRYPTION_KEY')
    cipher = Fernet(chave)
    return cipher.decrypt(cartao_criptografado).decode()

Nunca coloque chaves de criptografia no código. Use variáveis de ambiente, vaults (HashiCorp Vault, AWS Secrets Manager) ou sistemas de gerenciamento de segredos. Em desenvolvimento local, use .env com .gitignore.

Validação de Entrada e Prevenção de Ataques Comuns

SQL Injection, XSS e CSRF destroem APIs inseguras. A defesa começa com validação rigorosa de entrada. Não confie em dados que vêm do cliente — valide tipo, tamanho, formato e conteúdo.

Use bibliotecas especializadas para validação. Para SQL Injection, use prepared statements ou ORMs (SQLAlchemy, Django ORM). Para XSS, sanitize saída e use Content Security Policy headers. CSRF é menos preocupante em APIs stateless, mas CORS mal configurado expõe suas APIs.

# Exemplo com validação e proteção
from flask import Flask, request
from sqlalchemy import text
from pydantic import BaseModel, EmailStr, validator
import re

app = Flask(__name__)

# Validação com Pydantic (recomendado)
class CriarUsuario(BaseModel):
    email: EmailStr
    nome: str
    idade: int

    @validator('nome')
    def nome_valido(cls, v):
        if len(v) < 3 or len(v) > 100:
            raise ValueError('Nome deve ter 3-100 caracteres')
        if not re.match(r'^[a-zA-Z\s]+$', v):
            raise ValueError('Nome apenas com letras')
        return v

    @validator('idade')
    def idade_valida(cls, v):
        if v < 18 or v > 120:
            raise ValueError('Idade entre 18 e 120')
        return v

@app.route('/usuarios', methods=['POST'])
def criar_usuario():
    try:
        dados = CriarUsuario(**request.get_json())
    except ValueError as e:
        return {'erro': str(e)}, 400

    # Usar prepared statement (não concatenar strings!)
    query = text("INSERT INTO usuarios (email, nome, idade) VALUES (:email, :nome, :idade)")
    db.session.execute(query, {
        'email': dados.email,
        'nome': dados.nome,
        'idade': dados.idade
    })
    db.session.commit()

    return {'id': usuario.id}, 201

# Headers de segurança
@app.after_request
def adicionar_headers_seguranca(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    return response

Configure CORS restritivamente: liste domínios explícitos, não use * em produção. Implemente rate limiting para prevenir brute force e DDoS. Use bibliotecas como Flask-Limiter.

Logging, Monitoramento e Resposta a Incidentes

Você não pode proteger o que não monitora. Logar acessos, erros e comportamentos suspeitos é essencial. Mas cuidado: não logue senhas, tokens ou dados sensíveis. Estruture logs em JSON para análise automatizada.

Em produção, centralize logs (ELK Stack, Splunk, CloudWatch). Configure alertas para anomalias: múltiplas falhas de autenticação, alterações em dados críticos, requisições de IPs suspeitos. Ter um plano de resposta a incidentes — quem faz o quê quando a segurança é comprometida — é tão importante quanto evitar o incidente.

# Exemplo com logging estruturado
import logging
import json
from datetime import datetime

# Configurar logger JSON
class LoggerJSON(logging.Formatter):
    def format(self, record):
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'message': record.getMessage(),
            'module': record.module
        }
        return json.dumps(log_data)

logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(LoggerJSON())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

@app.route('/api/transferencia', methods=['POST'])
@token_obrigatorio
def transferencia():
    dados = request.get_json()
    logger.info({
        'acao': 'transferencia_iniciada',
        'usuario_id': request.usuario_id,
        'valor': dados['valor'],
        'para': dados['para_conta']
    })
    # Processar transferência
    logger.info({
        'acao': 'transferencia_concluida',
        'usuario_id': request.usuario_id
    })
    return {'sucesso': True}

Implemente versionamento de API para facilitar rollback de vulnerabilidades descobertas. Mantenha dependências atualizadas e faça auditorias de segurança regularmente com ferramentas como bandit (Python) ou npm audit (Node.js).

Conclusão

Dominar segurança em APIs é um processo contínuo. Os três pilares que você deve praticar imediatamente são: (1) Autenticação e autorização robustas com JWT, garantindo que apenas usuários legítimos acessem dados apropriados; (2) Proteção de dados com HTTPS, criptografia em repouso e bcrypt para senhas; (3) Validação rigorosa de entrada e monitoramento constante para detectar e responder a ataques antes que causem danos. Lembre-se: segurança não é um destino, é uma jornada de melhoria contínua.

Referências


Artigos relacionados