Checklist de Seguridad de APIs
Una checklist de seguridad essential para APIs: autenticación, autorización, validación de entrada, rate limiting, encriptación, logging y endurecimiento de despliegue.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Checklist de Seguridad de APIs
Introducción
Las APIs son la columna vertebral de las aplicaciones modernas — y una superficie de ataque primaria. Esta checklist cubre los controles de seguridad esenciales que toda API debe implementar, desde autenticación hasta endurecimiento de despliegue.
1. Autenticación
Usa Autenticación Basada en Tokens Fuertes
# Malo: API keys pasadas en query strings (logueadas por proxies)
GET /data?api_key=abc123
# Bueno: Bearer tokens en header Authorization
GET /data
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Implementa JWT de Forma Segura
import jwt
from datetime import datetime, timedelta
def create_access_token(user_id, secret, algorithm="HS256"):
payload = {
"sub": str(user_id),
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(minutes=15),
"jti": str(uuid.uuid4()) # ID único de token para revocación
}
return jwt.encode(payload, secret, algorithm=algorithm)
Checklist de Requisitos
- Usa HTTPS en todas partes (sin fallback HTTP)
- Los tokens expiran en 15 minutos o menos (access tokens)
- Refresh tokens expiran en 7-30 días con rotación
- Almacena tokens de forma segura (cookies HttpOnly para clientes browser)
- Rechaza tokens con firmas débiles (none, none256)
2. Autorización
Aplica el Principio de Mínimo Privilegio
# Malo: falta verificación de admin
def delete_user(user_id):
db.execute("DELETE FROM users WHERE id = %s", user_id)
# Bueno: verificar autorización antes de actuar
def delete_user(requesting_user, target_user_id):
if not requesting_user.has_role("admin"):
raise Forbidden("Rol admin requerido")
if requesting_user.id == target_user_id:
raise BadRequest("No puedes eliminarte a ti mismo")
db.execute("DELETE FROM users WHERE id = %s", target_user_id)
Checklist
- Autentica antes de autorizar (sin bypass de auth)
- Valida propiedad del recurso (usuario A no puede acceder datos de usuario B)
- Control de acceso basado en roles (RBAC) o atributos (ABAC)
- Denegar por defecto — permitir explícitamente, no confiar implícitamente
3. Validación de Entrada
Valida Todo
from pydantic import BaseModel, Field, validator
class CreateUserRequest(BaseModel):
email: str = Field(..., min_length=5, max_length=254)
password: str = Field(..., min_length=12, max_length=128)
@validator("email")
def validate_email(cls, v):
if "@" not in v:
raise ValueError("Formato de email inválido")
return v.lower().strip()
Checklist
- Valida tipo, longitud, formato y rango para cada entrada
- Rechaza campos inesperados (validación de esquema estricta)
- Sanitiza subidas de archivos (extensión, tipo MIME, límites de tamaño)
- Usa consultas parametrizadas (previene inyección SQL)
- Codifica salida para prevenir XSS
4. Rate Limiting
Previene Abuso
from flask_limiter import Limiter
limiter = Limiter(
key_func=lambda: request.headers.get("Authorization"),
default_limits=["100 per minute"]
)
@app.route("/api/login", methods=["POST"])
@limiter.limit("5 per minute")
def login():
...
Checklist
- Límites diferentes por endpoint (más estricto para auth, más laxo para lectura)
- Límites por usuario y por IP
- Retorna
429 Too Many Requestscon headerRetry-After - Loggea y alerta sobre violaciones repetidas
5. Encriptación
Datos en Tránsito
- TLS 1.2+ solamente
- Suites de cifrado fuertes (sin RC4, DES, MD5)
- Header HSTS para prevenir ataques de downgrade
- Certificate pinning para clientes móviles
Datos en Reposo
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher = Fernet(key)
# Encriptar campos sensibles
ssn_encrypted = cipher.encrypt(b"123-45-6789")
decrypted = cipher.decrypt(ssn_encrypted)
Checklist
- TLS 1.2+ para toda comunicación de API
- Encripta datos sensibles en reposo (PII, credenciales, tokens)
- Hashea contraseñas con bcrypt/Argon2 (nunca MD5 o SHA1)
- Gestión segura de claves (KMS, HSM, o vault — no en código)
6. Manejo de Errores
No Filtrues Información
# Malo: expone detalles internos
except DatabaseError as e:
return {"error": str(e)} # revela esquema, estructura de queries
# Bueno: mensaje genérico al cliente, log detallado server-side
except DatabaseError as e:
logger.error("Error de base de datos", exc_info=e, extra={"request_id": request.id})
return {"error": "Error interno del servidor"}, 500
Checklist
- Mensajes de error genéricos a clientes
- Logs detallados server-side (con IDs de correlación)
- Formato de error consistente (RFC 7807 Problem Details)
- No expongas stack traces, rutas de archivos ni info del sistema
7. Logging y Monitoreo
Qué Loggear
| Evento | Datos a Loggear | Datos a Evitar |
|---|---|---|
| Autenticación | Éxito/fallo, timestamp, IP | Contraseñas, tokens |
| Fallas de autorización | Recurso, acción, usuario | Payload sensible |
| Hits de rate limit | Usuario/IP, endpoint | Cuerpo completo de request |
| Errores | Tipo de error, ID de request, endpoint | Stack traces en logs de cliente |
Checklist
- Loggea todos los intentos de autenticación (éxito y fallo)
- Alerta sobre patrones anómalos (IPs inusuales, picos de volumen)
- Retén logs para investigación de incidentes (30-90 días)
- Agregación centralizada de logs (SIEM o equivalente)
8. CORS y Headers
# Política CORS estricta
from flask_cors import CORS
CORS(app, origins=["https://app.example.com"], supports_credentials=True)
# Headers de seguridad
@app.after_request
def add_security_headers(response):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["Strict-Transport-Security"] = "max-age=31536000"
return response
Checklist
- Restringe CORS a orígenes conocidos (no
*con credentials) - Establece headers de seguridad en todas las respuestas
- Desactiva banners de versión del servidor (nginx, Apache, framework)
9. Endurecimiento de Despliegue
- Ejecuta API en red aislada (VPC, subnets privadas)
- Usa un Web Application Firewall (WAF)
- Mantén dependencias actualizadas (escaneo automatizado de vulnerabilidades)
- Desactiva endpoints y métodos HTTP no usados
- Ejecuta con usuario de sistema de mínimo privilegio (no root)
Errores Comunes
- Confiar en validación del lado del cliente (siempre valida server-side)
- Almacenar secretos en variables de entorno sin rotación
- Usar IDs predecibles (
/user/1,/user/2) sin verificaciones de autorización - Faltar límites de paginación (DoS vía
?limit=999999) - CORS configurado a
*en producción
Preguntas Frecuentes
P: ¿Debería usar OAuth 2.0 o API keys para mi API? R: OAuth 2.0 para APIs orientadas a usuarios con integraciones de terceros. API keys son adecuadas para server-to-server donde la clave se mantiene secreta.
P: ¿Con qué frecuencia debo rotar las claves de firma? R: Al menos anualmente, o inmediatamente si hay compromiso. Usa versionado de claves para rotar sin downtime.
P: ¿Es GraphQL menos seguro que REST? R: No inherentemente, pero requiere controles diferentes: límites de profundidad de query, análisis de complejidad, y autorización a nivel de campo para prevenir agotamiento de recursos.
¿Cómo empiezo con esto en un proyecto existente?
Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.
¿Qué herramientas necesito?
Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.
¿Cómo mido el éxito después de implementar esto?
Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.
Temas Avanzados
Escenario: Hardening de API REST para Produccion
Sistema: API REST Node.js, 50 endpoints, 100K usuarios
Objetivo: Checklist completo de seguridad API
Checklist de seguridad API (40 items):
Autenticacion:
[x] JWT con RS256 (no HS256)
[x] Expiracion de token: 15 min access, 7 dias refresh
[x] Refresh token rotation
[x] MFA para endpoints admin
[x] Rate limiting en login: 5 intentos -> lock 15 min
[x] No exponer token en URL
[x] Logout invalida token (Redis blacklist)
[x] Password policy: min 12 chars, complejidad
Autorizacion:
[x] RBAC: roles user/admin/super_admin
[x] Verificar ownership en cada request (anti-IDOR)
[x] Scope por recurso: user solo accede sus datos
[x] Denegar por defecto, permitir explicitamente
[x] No auto-increment IDs (usar UUID)
Input:
[x] Schema validation (Zod) en cada endpoint
[x] Limite de tamano de payload (max 1MB)
[x] Sanitizacion de strings (no HTML injection)
[x] Queries parametrizadas (anti-SQL injection)
[x] No eval/exec con input de usuario
[x] File upload: validar tipo, tamano, contenido
Output:
[x] No exponer stack traces en prod
[x] DTO mapping: no exponer campos internos
[x] Headers: X-Content-Type-Options, X-Frame-Options, CSP, HSTS
[x] No exponer version del server/framework
[x] Rate limiting global: 100 req/min por usuario
Transporte:
[x] TLS 1.3 obligatorio (no TLS 1.0/1.1)
[x] Redirect HTTP -> HTTPS
[x] HSTS: max-age=31536000; includeSubDomains
[x] Certificate pinning (mobile apps)
Configuracion:
[x] CORS: origin estricto, no wildcard
[x] NODE_ENV=production
[x] Secrets en Secrets Manager (no .env en prod)
[x] Helmet() configurado
[x] Compression con Brotli (no gzip para evitar BREACH)
Logging y monitoreo:
[x] Audit log de acciones criticas
[x] No logear secrets, passwords, tokens, PII
[x] Alertas de intentos de auth fallidos
[x] Alertas de rate limit excedido
[x] SIEM integration (ELK + alerting)
Dependencias:
[x] npm audit en CI (--audit-level=high)
[x] Dependabot/Snyk configurado
[x] Lockfile en repo (package-lock.json)
[x] License check en CI
CI/CD:
[x] SAST (semgrep) en pipeline
[x] DAST (OWASP ZAP) en staging
[x] Container scan (Trivy) en build
[x] Secret scan (git-secrets) en pre-commit
[x] Code review obligatorio (1 approver min)
Lecciones:
- 40 items es el minimo para produccion
- IDOR es #1: verificar ownership siempre
- Schema validation en cada endpoint, sin excepciones
- Audit log + alerting = deteccion temprana
- SAST + DAST + secret scan en CI es obligatorio
Como priorizo si tengo poco tiempo?
Empieza por autenticacion (JWT seguro + rate limiting), autorizacion (verificar ownership), input validation (Zod en cada endpoint) y TLS. Estos 4 cubren el 80% de vulnerabilidades. Despues agrega headers (helmet), audit log, y dependency scanning. Finalmente, SAST/DAST en CI. La seguridad es un proceso continuo, no un proyecto unico.
Recursos Relacionados
Security Best Practices Guide
A thorough guide to application security: authentication, authorization, input validation, secrets management, and common vulnerability prevention.
GuideWeb Application Security (OWASP Top 10)
A developer-focused guide to the OWASP Top 10: injection, broken access control, XSS, insecure design, and how to prevent each vulnerability with code examples.
GuideREST API Design Guide
A thorough guide to designing clean, scalable, and maintainable REST APIs.
RecipeImplement Rate Limiting for APIs and Web Applications
How to protect APIs and web endpoints from abuse using token bucket, sliding window, and fixed window rate limiting strategies with Redis and in-memory implementations.
RecipeImplement Request Signing with HMAC
Secure API requests with HMAC signatures and AWS Signature v4 authentication for tamper-proof message integrity.
RecipeAPI Rate Limiting
Protect APIs from abuse and ensure fair resource usage with token bucket, sliding window, and leaky bucket rate limiting.
RecipeSSH Key Management in Bash
Generate, rotate, and distribute SSH keys with bash scripts