Skip to content
SP StackPractices
intermediate

Checklist de Seguridad de APIs

Una checklist de seguridad integral para APIs: autenticación, autorización, validación de entrada, rate limiting, encriptación, logging y endurecimiento de despliegue.

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.