Haciendo mi API más segura – De validaciones a bloqueo de ataques - Episodio 29

No pensé que mi API era tan vulnerable hasta que lo vi en los logs. 🔥🔥
Cuando empecé con la autenticación en mi API, asumí que con una simple validación de datos y un par de restricciones básicas estaría listo para producción.
Error.
Después de revisar los logs, me di cuenta de que:
🔹 Cualquiera podía enviar datos mal formateados en el registro de usuarios.
🔹 No había protección contra ataques de fuerza bruta en el login.
🔹 Errores internos exponían información sensible.
🔹 Flask no soporta HTTP/2, pero igual llegaban solicitudes con este protocolo.
Era hora de blindar la API.
📚 Problemas detectados y por qué importan
⚠️ 1️⃣ Falta de validación estructurada en /register
Antes, validaba manualmente los datos de usuario, lo que dejaba margen para errores y redundancia en el código.
Código antes:
@app.route('/register', methods=['POST'])
def register():
data = request.json
if not data or not data.get('email') or not data.get('password') or not data.get('username'):
return jsonify({"error": "Se requieren username, email y password"}), 400
🔴 Problema:
Dependía de validaciones manuales, lo que hacía el código menos mantenible y propenso a errores.
⚠️ 2️⃣ Ataques de fuerza bruta en /login
No había restricciones en la cantidad de intentos de login por usuario. Un atacante podía probar cientos de contraseñas sin límites.
🔴 Problema:
Sin restricciones, los atacantes podían explotar cuentas vulnerables fácilmente.
⚠️ 3️⃣ Errores internos exponiendo información
Cuando Flask encontraba un error inesperado, respondía con una página HTML estándar, filtrando detalles del servidor.
🔴 Problema:
Exponía información sensible, lo que podría ser usado por atacantes para encontrar vulnerabilidades.
⚠️ 4️⃣ Conexiones HTTP/2 no soportadas en Flask
Vimos en los logs intentos de conexión con HTTP/2, lo que Flask no soporta.
Estos intentos generaban errores innecesarios en los registros del servidor.
🔴 Problema:
Clientes incompatibles saturaban los logs con errores inútiles.
🛠️ Soluciones implementadas
✅ 1️⃣ Validación con Marshmallow en /register
Ahora usamos Marshmallow para estructurar la validación y eliminar redundancias.
Código mejorado:
from marshmallow import Schema, fields, validate, ValidationError
class UserSchema(Schema):
username = fields.String(required=True)
email = fields.Email(required=True)
password = fields.String(required=True, load_only=True, validate=[validate.Length(min=8)])
role = fields.String()
user_schema = UserSchema()
@app.route('/register', methods=['POST'])
def register():
try:
data = user_schema.load(request.json) # 🔥 Se valida automáticamente con Marshmallow
except ValidationError as e:
return jsonify(e.messages), 400
💡 Beneficios:
✔ Elimina validaciones manuales innecesarias.
✔ Garantiza que los datos sean correctos antes de ser procesados.
✔ Mejora la mantenibilidad del código.
✅ 2️⃣ Protección contra ataques de fuerza bruta (Rate Limiting)
Ahora limitamos los intentos de login a 5 por minuto con Flask-Limiter.
Código implementado:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # 🔥 Bloquea intentos masivos
def login():
pass # Implementación del login
💡 Beneficios:
✔ Evita ataques automatizados de fuerza bruta.
✔ Protege cuentas de usuarios reales.
✔ Reduce el consumo innecesario de la API.
✅ 3️⃣ Manejo global de errores
Ahora capturamos todos los errores inesperados con @app.errorhandler(Exception).
Código implementado:
import logging
@app.errorhandler(Exception)
def handle_unexpected_error(e):
logging.error(f"Error inesperado: {str(e)}")
return jsonify({"error": "Error interno del servidor"}), 500
💡 Beneficios:
✔ Evita que errores internos expongan información sensible.
✔ Centraliza la gestión de errores en un solo punto.
✔ Guarda un log estructurado en flask.log para auditoría.
💡 Lecciones aprendidas
🔹 No subestimes la importancia de validar datos de entrada correctamente.
🔹 Rate limiting es una capa de seguridad esencial para proteger cuentas de usuario.
🔹 Centralizar el manejo de errores facilita la depuración y mejora la experiencia del usuario.
🔹 HTTP/2 no es compatible con Flask, así que mejor bloquearlo desde el inicio.

💡 Reflexiones finales
Cuando empecé a trabajar en esta API, pensé que tenía todo bajo control.
Bueno a ver... todo, todo, es pasarse, pero cada vez que voy avanzando voy abriendo y descubriendo nuevos melones.
Así que para variar, la realidad me golpeó fuerte cuando vi los logs y comprendí que lo que consideraba "seguro" era, en realidad, un colador.
Este proceso no solo ha sido un reto técnico, sino también un viaje de aprendizaje personal.
Pero he aprendido a aceptar que el desarrollo de este proyecto es un ciclo continuo de errores y mejoras.
No se trata de evitar fallos, sino de aprender a corregirlos rápidamente y mejorar en cada iteración.
Sigo validando mi primera experiencia con la seguridad...
Es decir, que la seguridad no es algo que se añade al final, sino que debe ser parte del ADN del proyecto desde el principio.
Y más allá del código, este proceso me ha enseñado a pensar de manera más crítica, a cuestionar cada decisión y a buscar siempre formas de mejorar.
💭 ¿Te has encontrado con algún problema inesperado en tu proyecto?
¿Cómo lo superaste?
Cuéntamelo en los comentarios. 🚀
Si quieres conocer otros artículos parecidos a Haciendo mi API más segura – De validaciones a bloqueo de ataques - Episodio 29 puedes visitar la categoría Proyecto-IA.

Deja una respuesta