Skip to content
StackPractices
intermediate Por Mathias Paulenko

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 Requests con header Retry-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

EventoDatos a LoggearDatos a Evitar
AutenticaciónÉxito/fallo, timestamp, IPContraseñas, tokens
Fallas de autorizaciónRecurso, acción, usuarioPayload sensible
Hits de rate limitUsuario/IP, endpointCuerpo completo de request
ErroresTipo de error, ID de request, endpointStack 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.