Limitación de tasa y JWT WebSocket: asegurar antes del lanzamiento
Tengo una convicción: la seguridad se hace antes del lanzamiento, no después. Esperar a tener usuarios para asegurar tu aplicación es como instalar una cerradura después del robo. Así es como implementé el rate limiting y la autenticación JWT en los WebSockets de TAMSIV, incluso antes de tener un solo usuario.
Puntos clave a recordar:
- El rate limiting HTTP (100 req/15min/IP) y WebSocket (10 conn/min + 60 msg/min/usuario) protegen contra abusos y costos imprevistos.
- La autenticación JWT es requerida desde la conexión WebSocket, no después.
- Asegurar temprano cuesta menos tiempo y dinero que asegurar tarde.
- Cada mensaje WebSocket puede activar 3 APIs de pago; el rate limiting protege tu presupuesto.
¿Por qué asegurar una aplicación antes de tener usuarios?
La respuesta corta: por los costos. Cada llamada WebSocket en TAMSIV puede potencialmente activar tres APIs de pago: Deepgram para el STT, OpenRouter para el LLM, y OpenAI para el TTS. Sin rate limiting, un script malicioso —o incluso un simple error— podría generar cientos de euros en facturas en una noche.
La respuesta larga: por la arquitectura. Añadir el rate limiting después es refactorizar código por todas partes. Hacerlo desde el principio es un middleware limpio que se integra naturalmente en el pipeline vocal.
Y si empiezas con "ya veremos más tarde" para la seguridad, nunca lo harás. Es un principio que aprendí construyendo TAMSIV como desarrollador individual — cada atajo técnico se paga al céntuplo.
¿Cómo implementar el rate limiting HTTP en Express?
El backend Express de TAMSIV expone algunos endpoints REST (generación de imágenes, notificaciones push, administración). Sin protección, estos endpoints son vulnerables a ataques de fuerza bruta y DDoS.
Configuré un limitador de velocidad simple pero efectivo: 100 solicitudes por 15 minutos por IP. Aquí está el razonamiento:
- 100 solicitudes: suficiente para un uso normal (un usuario activo rara vez hace más de 20 solicitudes en 15 minutos), con un margen para picos.
- 15 minutos: ventana deslizante. Más corto (1 minuto) sería demasiado restrictivo. Más largo (1 hora) dejaría pasar demasiadas solicitudes abusivas.
- Por IP: el método más simple y universal. Las soluciones basadas en el ID de usuario no protegen contra usuarios no autenticados.
En la práctica, el middleware express-rate-limit se instala en 5 líneas. El encabezado Retry-After informa al cliente del tiempo de espera. El código HTTP 429 (Too Many Requests) es la respuesta estándar. Es una recomendación de OWASP que todo backend debería implementar.
¿Por qué el rate limiting de WebSocket es un verdadero desafío?
HTTP es fácil. WebSocket es otra historia. La conexión es persistente y los mensajes llegan en un flujo continuo. No puedes simplemente contar las "solicitudes" — cada segmento de audio genera un mensaje, y el STT envía resultados intermedios en streaming.
Implementé dos niveles de protección:
- 10 conexiones por minuto por usuario: evita el flood de conexiones. Un usuario normal abre 1-2 conexiones por sesión. 10 es un margen cómodo que atrapa los scripts.
- 60 mensajes por minuto por usuario: limita el flujo de mensajes en una conexión activa. Parece mucho, pero en streaming de audio, los chunks llegan rápido.
¿Por qué 60 mensajes por minuto? Porque durante una conversación de voz activa, el cliente envía fragmentos de audio cada segundo. Con el STT nativo o Deepgram, se añaden los resultados intermedios. 60 mensajes dejan un margen de seguridad mientras bloquean los abusos evidentes.
La dificultad adicional: el conteo debe ser por usuario, no por conexión. Un usuario malicioso podría abrir 9 conexiones y enviar 59 mensajes en cada una, eludiendo un límite de velocidad por conexión. El conteo por user-id (extraído del JWT) resuelve este problema.
¿Cómo asegurar la autenticación JWT en los WebSockets?
La autenticación WebSocket es fundamentalmente diferente de HTTP. Con HTTP, envías el token en el encabezado Authorization en cada solicitud. Con WebSocket, la autenticación se realiza una sola vez, en la conexión.
En TAMSIV, el token JWT de Supabase es requerido desde la conexión:
ws://backend:3001?token=eyJhbGciOiJIUzI1NiIs...
El servidor verifica el token antes de aceptar la conexión. Si el token es inválido, ha expirado o está ausente: conexión rechazada inmediatamente. Sin mensaje de error detallado (para evitar la fuga de información), solo un código de cierre de WebSocket 4401.
¿Por qué el token está en la cadena de consulta y no en un encabezado? Porque la API WebSocket del navegador no admite encabezados personalizados en la conexión. Es una limitación conocida del protocolo. La alternativa es enviar el token como primer mensaje, pero eso deja una ventana en la que la conexión no está autenticada, lo cual es inaceptable para TAMSIV.
El token también está disponible a través del encabezado Authorization: Bearer xxx para los clientes que lo admiten (como las bibliotecas de Node.js). El servidor acepta ambos métodos.
¿Cuáles son los costos reales de una falla de seguridad para un desarrollador individual?
Para una startup o un desarrollador individual, una falla de seguridad no es solo un problema técnico, es un problema financiero. Aquí están los escenarios que el rate limiting previene:
- Escenario 1 — Error del cliente: un bucle infinito en el frontend envía miles de mensajes WebSocket. Sin rate limiting: factura de API explosiva. Con: 60 mensajes máximo, luego bloqueo.
- Escenario 2 — Script malicioso: alguien utiliza la API para generar contenido a través del LLM. Sin rate limiting: uso ilimitado a tu cargo. Con: bloqueado después de 100 solicitudes/15min.
- Escenario 3 — DDoS ligero: un bot martillea los endpoints. Sin rate limiting: backend sobrecargado, aplicación inaccesible. Con: las solicitudes excesivas son rechazadas antes de llegar a la lógica de negocio.
El sistema de alertas de administración que implementé envía un correo electrónico a través de Resend cuando un usuario alcanza el 80% del límite de velocidad. Esto me permite intervenir antes de que el problema se vuelva crítico.
¿Cómo probar la seguridad de una aplicación antes del lanzamiento?
La auditoría de seguridad de TAMSIV cubrió varios ejes:
- Verificación de rate limiting: scripts de prueba que envían ráfagas de solicitudes y verifican que el bloqueo se activa en el umbral correcto.
- Validación de JWT: pruebas con tokens expirados, mal formados, modificados (tampering) y tokens de otros proyectos de Supabase.
- Políticas RLS: verificación de que cada tabla del esquema de la base de datos tiene políticas de acceso correctas. Más de 30 políticas RLS probadas individualmente.
- Sanitización de entrada: verificación de que los mensajes WebSocket son válidos y que las inyecciones están bloqueadas.
- Configuración de CORS: solo los dominios autorizados (tamsiv.com, IPs de desarrollo) pueden conectarse al backend.
Estas pruebas forman parte del conjunto de pruebas automatizadas del backend. Cada despliegue a través de railway up verifica que la seguridad no ha retrocedido.
¿Cuáles son las buenas prácticas de seguridad para los WebSockets en producción?
Aquí está la lista de verificación que seguí para TAMSIV, basada en las recomendaciones de OWASP:
- Autenticación en la conexión: JWT verificado antes de aceptar la actualización de WebSocket.
- Rate limiting de dos niveles: conexiones y mensajes, ambos por usuario.
- Validación de mensajes: cada mensaje es válido (formato, tamaño máximo, tipo) antes de su procesamiento.
- Tiempos de espera de seguridad: conexiones inactivas cerradas después de 5 minutos. El AudioPlayerService también tiene un tiempo de espera de 30 segundos para la limpieza.
- Registro de anomalías: cada límite de velocidad alcanzado, cada intento de conexión fallido se registra para su análisis.
- CORS estricto: lista blanca de orígenes autorizados, actualizada con cada cambio de dominio.
- TLS obligatorio: en producción, solo se acepta
wss://.
Este enfoque de seguridad primero se alinea con la filosofía de arquitectura limpia del proyecto. La seguridad no es una capa añadida, es un componente arquitectónico al mismo nivel que el enrutamiento o la base de datos.
¿Qué impacto tiene el rate limiting en la experiencia del usuario?
Un rate limiting bien configurado es invisible para el usuario normal. Los umbrales están calibrados para que un uso legítimo nunca los alcance. Pero cuando un usuario los alcanza, la experiencia debe seguir siendo clara:
- Lado HTTP: código 429 con encabezado
Retry-Aftery mensaje explicativo. - Lado WebSocket: mensaje de error típico antes del cierre de la conexión.
- Lado frontend: notificación no bloqueante que informa al usuario que debe reducir la velocidad.
El sistema de notificaciones gestiona estos casos con elegancia. El usuario sabe lo que está sucediendo sin ser bloqueado bruscamente.
Preguntas Frecuentes
¿El rate limiting afecta las conversaciones de voz en streaming?
No, para un uso normal. El umbral de 60 mensajes por minuto está calibrado para acomodar el streaming de audio y los resultados intermedios del STT. Un usuario que habla normalmente nunca supera este umbral. Si se alcanza el umbral, es señal de un error del cliente o de un uso abusivo.
¿Cómo gestiona TAMSIV los tokens JWT caducados durante una sesión?
El token JWT de Supabase tiene una vida útil de una hora. El cliente frontend actualiza el token automáticamente antes de que expire. Si la conexión WebSocket utiliza un token caducado, el servidor devuelve un código de cierre específico y el cliente se reconecta con un nuevo token.
¿El rate limiting es configurable por usuario?
Los umbrales predeterminados se aplican a todos los usuarios. El panel de administración permite ver a los usuarios que se acercan a los límites y ajustar los umbrales globales si es necesario. Una evolución prevista permitirá umbrales diferenciados por plan de suscripción (Free vs Pro vs Team).
¿Por qué no usar un servicio de terceros como Cloudflare para el rate limiting?
Cloudflare gestiona bien el rate limiting HTTP, pero no cubre los WebSockets de manera granular. El rate limiting a nivel de aplicación en TAMSIV permite un control fino por usuario y por tipo de mensaje, algo que un WAF externo no puede ofrecer. Ambas aproximaciones son complementarias.
¿Cómo verificar que el rate limiting funciona correctamente?
El backend incluye pruebas automatizadas que simulan ráfagas de solicitudes y verifican que las respuestas HTTP 429 llegan al umbral correcto. Los registros de producción muestran el número de solicitudes bloqueadas por día, lo que permite ajustar los umbrales continuamente.