Posted on 17 junio, 2026 / Under Desarrollo, Full Stack, Seguridad, Backend, DevOps


Introducción: El Problema de la Identidad en Sistemas Distribuidos

En el desarrollo de aplicaciones web modernas, uno de los retos más críticos no es únicamente construir funcionalidades, sino garantizar que solo los usuarios autorizados puedan acceder a ellas. Esta problemática, aparentemente sencilla, esconde una complejidad técnica considerable cuando nos alejamos de los sistemas monolíticos tradicionales y nos adentramos en arquitecturas donde el frontend y el backend son sistemas completamente independientes que se comunican exclusivamente a través de una API REST.

En los sistemas tradicionales basados en sesiones de servidor, el flujo era simple: el usuario iniciaba sesión, el servidor creaba una sesión en memoria y devolvía al navegador una cookie con el identificador de esa sesión. En cada petición posterior, el servidor consultaba su memoria para verificar si esa sesión seguía activa. Este enfoque funcionaba bien en entornos monolíticos, pero presenta problemas graves en sistemas modernos: si tienes múltiples instancias del servidor corriendo en paralelo, cada una tiene su propia memoria y no comparte las sesiones con las demás. Escalar horizontalmente se convierte en un problema complejo.

Es en este contexto donde JSON Web Token, conocido como JWT, se ha consolidado como el estándar de facto para la autentificación sin estado en aplicaciones modernas. JWT traslada la responsabilidad del estado del servidor al cliente, eliminando la necesidad de que el servidor recuerde nada entre peticiones.

En este artículo analizaremos en profundidad qué es un JWT, cómo funciona internamente, qué riesgos conlleva una mala configuración y cómo implementarlo de forma segura y robusta. Para ilustrarlo usaremos como referencia real el proyecto MemoireNomade, una plataforma de reservas turísticas desarrollada con React 18 y ASP.NET Core 8, desplegada en Microsoft Azure.


¿Qué es JWT y por qué existe?

JWT son las siglas de JSON Web Token. Es un estándar abierto definido en la RFC 7519 que establece un mecanismo compacto, autónomo y seguro para transmitir información entre dos partes como un objeto JSON firmado digitalmente.

La palabra clave aquí es autónomo. A diferencia de las sesiones tradicionales, donde el servidor necesita consultar su memoria o base de datos para saber si un usuario está autenticado, un JWT contiene toda la información necesaria dentro de sí mismo. El servidor solo necesita verificar que la firma del token es válida para confiar en su contenido.

Esto tiene una implicación arquitectónica muy importante: el servidor se vuelve stateless, sin estado. No importa cuántas instancias del servidor estén corriendo en paralelo, todas pueden verificar el mismo token sin necesidad de compartir información entre ellas. Esto hace que JWT sea especialmente adecuado para entornos en la nube y arquitecturas de microservicios.

El flujo completo de autentificación con JWT funciona de la siguiente manera:

1. El usuario envía sus credenciales (usuario + contraseña)
              ↓
2. El servidor verifica las credenciales contra la base de datos
              ↓
3. Si son correctas, el servidor genera un JWT firmado con una clave secreta
              ↓
4. El servidor envía el JWT al cliente (navegador)
              ↓
5. El cliente guarda el token (en memoria o almacenamiento local)
              ↓
6. En cada petición posterior, el cliente envía el token en la cabecera HTTP:
   Authorization: Bearer <token>
              ↓
7. El servidor recibe el token, verifica su firma y su expiración
              ↓
8. Si todo es válido, procesa la petición. Si no, devuelve un error 401.

Este flujo garantiza que el servidor nunca necesita recordar qué usuarios están conectados. Cada petición es completamente independiente y se verifica por sí sola.


Anatomía de un JWT: Las Tres Partes

Uno de los aspectos más interesantes de JWT es su estructura. Un token tiene el siguiente aspecto:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6IkFkbWluIiwiZXhwIjoxNzE4NjQwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

A primera vista parece ilegible, pero en realidad son tres bloques de texto codificados en Base64 separados por puntos. Cada bloque tiene un propósito específico:

1. Header (Cabecera)

El primer bloque contiene metadatos sobre el propio token: qué tipo de token es y qué algoritmo de firma se ha usado.

json

{
  "alg": "HS256",
  "typ": "JWT"
}

El algoritmo HS256 significa HMAC con SHA-256, uno de los más usados por su equilibrio entre seguridad y rendimiento. Existen otros algoritmos como RS256 (basado en criptografía asimétrica con par de claves pública/privada), que se usan en escenarios donde múltiples servicios necesitan verificar el token sin tener acceso a la clave secreta.

2. Payload (Cuerpo)

El segundo bloque es el más importante desde el punto de vista funcional. Contiene los claims, que son las afirmaciones o datos que el servidor incluye sobre el usuario autenticado.

json

{
  "sub": "1234567890",
  "name": "Giovanny Panesso",
  "role": "Admin",
  "iat": 1718596800,
  "exp": 1718640000
}

Existen tres tipos de claims:

  • Claims registrados: Estandarizados por la especificación JWT. Los más importantes son sub (sujeto, normalmente el ID del usuario), iat (fecha de emisión), exp (fecha de expiración) y iss (emisor del token).
  • Claims públicos: Definidos por la comunidad y registrados en el IANA JSON Web Token Registry para evitar colisiones.
  • Claims privados: Definidos por el propio equipo de desarrollo para las necesidades concretas de la aplicación, como el rol del usuario o sus permisos específicos.

Es fundamental entender que el payload no está cifrado, solo está codificado en Base64. Cualquiera puede decodificarlo y leer su contenido. Por este motivo nunca se deben incluir datos sensibles como contraseñas, números de tarjeta o información personal crítica dentro del token.

3. Signature (Firma)

La firma es el mecanismo que garantiza la integridad del token y es la parte más crítica desde el punto de vista de la seguridad. Se genera de la siguiente forma:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secretKey
)

El servidor toma el header y el payload codificados, los combina con una clave secreta que solo él conoce, y aplica el algoritmo de firma. El resultado es la firma.

Cuando el servidor recibe un token en una petición posterior, repite este proceso y compara la firma resultante con la que viene en el token. Si coinciden, el token es auténtico y no ha sido manipulado. Si alguien modifica cualquier dato del payload (por ejemplo, cambiando su rol de «User» a «Admin»), la firma dejará de coincidir y el servidor rechazará el token inmediatamente.


JWT en MemoireNomade: Implementación Real

En el proyecto MemoireNomade, JWT es el mecanismo que protege todo el panel de administrador. Las rutas /admin/dashboard, /admin/tours y /admin/bookings están protegidas mediante un componente ProtectedRoute en el frontend React que verifica si existe un token válido antes de renderizar la página. Si no hay token o ha vencido, el usuario es redirigido automáticamente al login.

En el backend, la lógica de generación del token está encapsulada en el AuthService. El código que crea el JWT es el siguiente:

csharp

var token = new JwtSecurityToken(
    issuer: _jwtSettings.Issuer,
    audience: _jwtSettings.Audience,
    claims: claims,
    expires: DateTime.UtcNow.AddHours(_jwtSettings.ExpirationHours),
    signingCredentials: credentials
);

Cada parámetro tiene un papel concreto y deliberado:

  • Issuer: Identifica quién emite el token. En este caso es la propia API de MemoireNomade. El cliente puede verificar que el token proviene de la fuente esperada y no de un servicio externo malicioso.
  • Audience: Define para quién va dirigido el token. Solo el frontend de MemoireNomade debería aceptar y usar este token. Esto evita que un token generado para un servicio sea reutilizado en otro.
  • Claims: Los datos del usuario que viajan dentro del token, como su ID y su rol de administrador.
  • Expires: La fecha y hora exacta en que el token dejará de ser válido. Esta línea en concreto es la protagonista del experimento que veremos a continuación.
  • SigningCredentials: Las credenciales de firma que garantizan la integridad del token, generadas a partir de la clave secreta configurada en el servidor.

La configuración de todos estos parámetros se gestiona de forma centralizada desde el archivo appsettings.json:

json

"JwtSettings": {
  "SecretKey": "__SET_IN_USER_SECRETS__",
  "Issuer": "MemoireNomade.API",
  "Audience": "MemoireNomade.Frontend",
  "ExpirationHours": 8
}

Merece especial atención el valor de SecretKey. En lugar de almacenar la clave secreta directamente en el archivo de configuración, que podría quedar expuesta en el historial de Git o en el registro de imágenes Docker, se usa el mecanismo de User Secrets de .NET en desarrollo y variables de entorno seguras en el entorno de Azure en producción. La clave nunca viaja en el código fuente.


El Riesgo de una Configuración Incorrecta: El Experimento

Para ilustrar de forma práctica la importancia de configurar correctamente el tiempo de expiración, durante el desarrollo del vídeo asociado a este artículo se realizó un experimento deliberado. Se redujo el valor de ExpirationHours a un número prácticamente nulo:

json

"ExpirationHours": 0.001

Este valor equivale a aproximadamente 3.6 segundos. El resultado fue inmediato y contundente: al iniciar sesión en el panel de administrador y realizar cualquier petición al backend, el sistema detectaba que el token ya había vencido y redirigía automáticamente al login sin posibilidad de continuar.

Este experimento, aunque intencionadamente extremo, refleja situaciones reales que ocurren en producción cuando los equipos de desarrollo no prestan suficiente atención a esta configuración. Pero el problema no es solo en una dirección. Existen dos escenarios igualmente problemáticos y opuestos:

Escenario 1: Token con expiración demasiado corta

Cuando el token vence en un tiempo excesivamente breve, las consecuencias son principalmente de usabilidad:

  • El usuario pierde la sesión constantemente mientras trabaja, interrumpiendo su flujo de trabajo.
  • En aplicaciones con procesos largos, como la gestión de reservas o la edición de contenido, el token puede vencer en medio de una operación crítica, perdiendo el trabajo realizado.
  • La experiencia de usuario se degrada hasta el punto de hacer la aplicación prácticamente inutilizable.
  • El equipo de soporte recibe quejas continuas sobre «cierres de sesión inesperados».

Escenario 2: Token con expiración demasiado larga o sin expiración

Este escenario es más silencioso pero mucho más peligroso desde el punto de vista de la seguridad:

  • Si un atacante consigue interceptar o robar un token, tendrá acceso completo al sistema durante todo el tiempo que dure ese token.
  • En aplicaciones administrativas como MemoireNomade, esto significaría acceso total a las reservas, datos de clientes, configuración de tours y capacidad de modificar cualquier dato del sistema.
  • Sin expiración, no existe ningún mecanismo automático que invalide el acceso de un atacante. Solo un cambio manual de la clave secreta, que invalidaría todos los tokens activos incluyendo los legítimos, podría solucionar el problema.
  • Este tipo de vulnerabilidad es especialmente crítica en entornos donde los usuarios acceden desde dispositivos compartidos o redes públicas.

La Solución Óptima: Combinar JWT con Refresh Token

Para resolver el dilema entre seguridad y usabilidad, MemoireNomade implementa un sistema de Refresh Token gestionado por el AuthService. Este patrón es el más recomendado por la industria y el que usan la mayoría de aplicaciones modernas como referencia.

La idea es simple pero elegante: en lugar de usar un único token de larga duración, se usan dos tokens con propósitos distintos:

Token de acceso (Access Token)
  → Duración corta: 8 horas
  → Se envía en cada petición al backend
  → Si es robado, el daño es limitado en el tiempo

Refresh Token
  → Duración larga: 30 días
  → Se guarda de forma segura en el cliente
  → Solo se usa para obtener un nuevo Access Token cuando el actual vence
  → Si es robado, puede invalidarse individualmente en el servidor

El flujo completo con Refresh Token funciona así:

1. El usuario inicia sesión
              ↓
2. El servidor devuelve un Access Token (8h) + un Refresh Token (30 días)
              ↓
3. El cliente usa el Access Token en cada petición
              ↓
4. Cuando el Access Token vence (a las 8 horas)
              ↓
5. El cliente envía el Refresh Token al servidor
              ↓
6. El servidor verifica el Refresh Token y emite un nuevo Access Token
              ↓
7. El usuario continúa sin necesidad de volver a hacer login

Este mecanismo se refleja en la configuración del proyecto:

json

"JwtSettings": {
  "ExpirationHours": 8
},
"RefreshTokenSettings": {
  "ExpirationDays": 30
}

La ventaja de este sistema es que si se detecta un acceso sospechoso, el servidor puede invalidar el Refresh Token específico de ese usuario sin afectar al resto de sesiones activas. Esto da al equipo de seguridad un control granular sobre las sesiones activas del sistema.


Matriz Comparativa de Estrategias de Configuración

Para sintetizar las implicaciones de cada estrategia de configuración, la siguiente tabla resume los principales escenarios:

EstrategiaExpiración Access TokenRefresh TokenSeguridadExperiencia de UsuarioRecomendado para
Sin expiraciónNuncaNo❌ Crítica✅ BuenaNunca
Expiración muy cortaSegundos / MinutosNo✅ Alta❌ HorribleNunca
Expiración corta + Refresh15 – 60 minutos✅ Muy alta✅ ExcelenteApps bancarias, datos sensibles
Expiración media + Refresh8 horas✅ Alta✅ Muy buenaApps empresariales, paneles de gestión
Expiración larga sin Refresh24 – 72 horasNo⚠️ Media✅ BuenaAPIs internas de bajo riesgo

Despliegue en Azure y Buenas Prácticas de Seguridad

MemoireNomade está desplegado en Microsoft Azure en la región de Francia Central, usando dos Container Apps independientes: una para el frontend en React servido por nginx y otra para el backend en ASP.NET Core 8. Esta arquitectura de contenedores hace que la correcta gestión de los secretos sea aún más crítica.

En este entorno, la clave secreta del JWT sigue las siguientes buenas prácticas:

En desarrollo local:
La clave se gestiona mediante el sistema de User Secrets de .NET, que almacena los valores fuera del directorio del proyecto y nunca los incluye en el repositorio de Git. Por eso en el appsettings.json aparece el valor __SET_IN_USER_SECRETS__ como recordatorio.

En producción en Azure:
La clave se inyecta como variable de entorno segura en la configuración del Container App, sin que aparezca nunca en el código fuente ni en la imagen Docker almacenada en el Azure Container Registry.

Adicionalmente, una clave secreta robusta debe cumplir los siguientes requisitos:

  • Longitud mínima de 32 caracteres para el algoritmo HS256.
  • Combinación de letras mayúsculas, minúsculas, números y caracteres especiales.
  • Generada de forma aleatoria, nunca basada en palabras o frases reconocibles.
  • Rotada periódicamente como parte de la política de seguridad del sistema.

Conclusión: JWT como Decisión de Arquitectura

A lo largo de este artículo hemos visto que JWT va mucho más allá de ser simplemente una forma de «hacer login» en una aplicación web. Es un mecanismo de autentificación sin estado que resuelve problemas fundamentales de escalabilidad y distribución en sistemas modernos, pero que requiere una configuración cuidadosa y deliberada.

Como hemos demostrado de forma práctica con el proyecto MemoireNomade, un valor incorrecto en el tiempo de expiración puede convertir una aplicación funcional en un sistema inutilizable o, en el extremo opuesto, en un sistema vulnerable a ataques de robo de sesión. La diferencia entre un sistema seguro y uno comprometido puede estar en un único número en el archivo de configuración.

La implementación del patrón Access Token más Refresh Token no es una complejidad innecesaria. Es la solución que la industria ha desarrollado a lo largo de años de experiencia para encontrar el equilibrio correcto entre seguridad y usabilidad, y su adopción debe considerarse una buena práctica estándar en cualquier aplicación que maneje datos de usuarios.

Diseñar la autentificación correctamente desde el principio no es un detalle de implementación. Es una decisión de arquitectura que define la postura de seguridad de todo el sistema.


Recursos y Bibliografía

  • Jones, M., Bradley, J., & Sakimura, N. (2015). JSON Web Token (JWT). IETF RFC 7519. Disponible en datatracker.ietf.org/doc/html/rfc7519
  • Jones, M. & Hildebrand, J. (2015). JSON Web Token Best Current Practices. IETF RFC 8725. Disponible en datatracker.ietf.org/doc/html/rfc8725
  • Microsoft Docs. (2024). Authentication and Authorization in ASP.NET Core. Disponible en learn.microsoft.com
  • OWASP Foundation. (2023). JSON Web Token Security Cheat Sheet. Disponible en cheatsheetseries.owasp.org
  • Auth0. (2024). The Anatomy of a JSON Web Token. Disponible en auth0.com/blog/id-token-access-token-what-is-the-difference

Autor: Giovanny Alejandro Panesso Rodriguez

Máster: Desarrollo Full Stack + Arquitecturas Cloud

Centro: Tajamar Tech

Año académico: 2025-2026

Código / recursos utilizados:

GitHub: (https://github.com/GiovannyPanesso/memoire-nomade)

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.