Una sesión guarda en el servidor quién eres y te da una cookie con un identificador aleatorio. Un JWT es un token firmado que lleva tus datos dentro y que el servidor comprueba por su firma. OAuth 2.0 es otra cosa: el protocolo con el que una app obtiene permiso para acceder a tus datos en otro servicio.
La confusión viene de que se comparan como si fueran tres opciones del mismo menú. No lo son. Sesión y JWT son dos maneras de recordar que ya te has identificado; OAuth es la manera de pedir permiso a otro servicio, y muchas veces sus tokens son JWT. Por eso la pregunta útil no es «¿cuál es mejor?», sino «¿qué problema tengo?».
Para que no se quede en teoría, cada concepto va con un ejemplo mínimo en Python que hemos ejecutado el 10 de noviembre de 2026 (Python 3.13, Flask 3.1 y PyJWT 2.15). Las salidas que verás son las reales. Las definiciones salen de los estándares: los RFC del IETF, las guías de OWASP y la documentación de MDN.
Qué es una sesión con cookie y cómo funciona
Una sesión con cookie funciona así: cuando inicias sesión, el servidor crea un registro con tus datos, genera un identificador aleatorio y te lo envía en una cookie. En cada petición, el navegador devuelve esa cookie y el servidor busca el registro. El estado vive en el servidor; tu navegador solo guarda una referencia sin significado.
Es el modelo más antiguo de la web y sigue siendo el más sencillo de hacer bien en una aplicación con su propio backend. La seguridad depende de tres cosas, todas recogidas en la guía de gestión de sesiones de OWASP:
Que el identificador no se pueda adivinar. OWASP exige al menos 64 bits de entropía y un generador criptográficamente seguro.
Que la cookie no quede al alcance de JavaScript. El atributo HttpOnly impide leerla desde
document.cookie, que es lo que buscaría un ataque XSS. MDN lo resume así: las cookies que mantienen una sesión del lado del servidor no necesitan estar disponibles para JavaScript.Que el servidor pueda acabar con ella. Cerrar sesión es borrar el registro. En ese instante la cookie deja de valer, aunque alguien la hubiera copiado.
Este es el ejemplo mínimo con Flask. Guarda las sesiones en un diccionario para que se entienda la idea; en producción irían en Redis o en una base de datos.
Salida real:
Fíjate en la última línea: tras el logout, la misma cookie ya no abre nada. Esa capacidad de invalidar al momento es lo que más se echa de menos cuando se pasa a JWT sin pensarlo.
Dos detalles que OWASP marca como obligatorios y que el ejemplo no cubre. Primero, hay que regenerar el identificador al iniciar sesión y en cada cambio de privilegios, para evitar la fijación de sesión. Segundo, SameSite debe declararse siempre de forma explícita: OWASP prefiere Strict y acepta Lax, y recomienda además el prefijo __Host- en el nombre de la cookie. Usamos Lax porque con Strict el usuario que llega desde un enlace externo aparece sin sesión, algo que en muchas apps confunde más de lo que protege. Si tu app maneja datos sensibles, ve a Strict.
Qué es un JWT y qué lleva dentro
Un JWT (JSON Web Token) es, según el RFC 7519, un medio compacto y seguro para URL de representar claims, es decir, afirmaciones que se transfieren entre dos partes. En su forma habitual va firmado: tiene tres partes separadas por puntos (cabecera, contenido y firma) y quien tenga la clave puede comprobar que nadie lo ha alterado.
Lo que suele sorprender es que firmado no significa cifrado. El contenido va codificado en base64url, no protegido: cualquiera que tenga el token puede leerlo. La firma solo garantiza que no se ha modificado. Por eso en un JWT nunca deben ir contraseñas ni datos que no enseñarías.
Dentro van los claims. El RFC 7519 define varios registrados, y la guía de JWT de OWASP destaca los que importan para la seguridad:
Claim | Qué indica | Por qué validarlo |
|---|---|---|
| Momento de caducidad | Pasado ese momento, el RFC dice que el token no debe aceptarse |
| Quién lo emitió | Evita aceptar tokens de un emisor que no es el tuyo |
| Para quién es | Evita que un token emitido para otra API sirva en la tuya |
| De quién habla (el usuario) | Identifica al sujeto |
| Cuándo se emitió | Permite saber la antigüedad del token |
El ejemplo firma un token con HS256, lo lee sin clave para demostrar que no está cifrado, lo verifica y prueba cuatro intentos de colarlo: contenido manipulado, token caducado, audiencia equivocada y el famoso alg: none.
Salida real:
La línea que más enseña es algorithms=["HS256"]. Decir explícitamente qué algoritmo aceptas es lo que bloquea el alg: none: OWASP recuerda que algunas librerías aceptaban antes tokens sin firma por defecto, y con eso cualquiera podía fabricarse el suyo. La clave del ejemplo se genera en cada ejecución con secrets y tiene 256 bits, el tamaño que OWASP pide para HS256. Una contraseña o una cadena escrita en el código no valen como clave.
Sesión o JWT: la diferencia está en dónde vive el estado
La diferencia de fondo entre una sesión y un JWT es dónde se guarda el estado. En la sesión vive en el servidor y la cookie solo lo señala; en el JWT viaja dentro del token y el servidor lo cree mientras la firma y la caducidad cuadren. De ahí salen sus ventajas y sus problemas, que son simétricos.
Sesión con cookie | JWT firmado | |
|---|---|---|
Dónde está el estado | En el servidor (Redis, base de datos) | Dentro del token |
Qué hace el servidor en cada petición | Busca el identificador | Verifica firma, |
Cerrar sesión al momento | Sí: se borra el registro | No, salvo que mantengas una lista de revocados |
Quién puede leer el contenido | Nadie fuera del servidor | Cualquiera que tenga el token |
Dónde encaja mejor | Una app web con su propio backend | APIs que verifican tokens emitidos por otro servicio, como en OAuth |
El punto más delicado es la revocación. OWASP lo dice sin rodeos en su guía de JWT: si los usas para sesiones de usuario necesitas una lista de tokens revocados, y en cuanto la tienes la sesión ya no es «sin estado», que era justo la ventaja que buscabas. Su propia recomendación es considerar un sistema de sesiones normal.
Nuestro criterio, para quien empieza y se siente tentado por el JWT porque parece lo moderno: si tu frontend y tu backend son la misma aplicación, empieza con sesiones. Son más fáciles de hacer bien y cerrar sesión funciona de verdad. Reserva el JWT para cuando un token tiene que ser verificado por servicios que no comparten base de datos con quien lo emitió, que es lo que ocurre con los tokens de acceso y de identidad en OAuth y OpenID Connect.
Y una regla que no admite matices: OWASP pide no guardar tokens de sesión, JWT ni refresh tokens en localStorage ni en sessionStorage, porque cualquier script de la página puede leerlos y basta un fallo de XSS para exponerlos todos. Si un JWT tiene que vivir en el navegador, mejor en una cookie HttpOnly.
Del ejemplo de veinte líneas al login de un producto real
En Rock the AI Code aprendes a implementar autenticación y pagos: login de usuarios con JWT, OAuth y Clerk, y cobros con Stripe y webhooks. Descarga el dossier y descubre los 8 proyectos que construirás y el syllabus completo.
Qué es OAuth 2.0 y por qué no es un sistema de login
OAuth 2.0 es, según el RFC 6749, un marco de autorización que permite a una aplicación de terceros obtener acceso limitado a un servicio HTTP en nombre de su propietario. Resuelve un problema concreto: que una app use tus datos de otro servicio sin que tengas que darle tu contraseña.
Antes de OAuth, si una aplicación quería leer tu calendario, le dabas tu usuario y tu contraseña del calendario. El RFC 6749 explica en su introducción por qué eso era un problema: la app guardaba tu contraseña, tenía acceso a todo y no podías retirarle el permiso a ella sola sin cambiar la contraseña. OAuth sustituye la contraseña por un token de acceso limitado a unos permisos (los scopes) y revocable.
El RFC define cuatro roles, y entenderlos ahorra media confusión:
Propietario del recurso: tú, la persona que concede el acceso.
Cliente: la aplicación que quiere acceder a tus datos.
Servidor de autorización: el que te autentica, te pregunta si das permiso y emite los tokens.
Servidor de recursos: la API que guarda tus datos y acepta los tokens.
Entonces, ¿por qué se usa OAuth para «Iniciar sesión con…»? Porque sobre sus flujos se construye OpenID Connect. El RFC 9700, que recoge las buenas prácticas actuales de seguridad de OAuth, describe OAuth 2.0 como el estándar para proteger APIs y la base del inicio de sesión federado con OpenID Connect. En OpenID Connect, como recuerda OWASP, la identidad del usuario llega en un ID token que es un JWT. Así se juntan las tres piezas del título.
El flujo de código de autorización con PKCE, paso a paso
El flujo de código de autorización con PKCE es la forma recomendada hoy de usar OAuth en apps web, SPA y móviles. El RFC 9700 obliga a los clientes públicos a usar PKCE y lo recomienda para los confidenciales. Su idea es simple: quien pide el código tiene que demostrar después que es el mismo que lo canjea.
Estos son los pasos, con los nombres de los parámetros que verás en cualquier proveedor:
Tu app prepara el secreto de un solo uso. Genera un
code_verifieraleatorio (el RFC 7636 pide al menos 43 caracteres) y calcula su huella:code_challenge = BASE64URL(SHA256(code_verifier)). Ese método se llama S256.Tu app redirige al usuario al servidor de autorización con
response_type=code, suclient_id, laredirect_uri, losscopeque pide, elcode_challengeycode_challenge_method=S256. El verificador no viaja: se queda en tu app.El usuario se identifica en el servidor de autorización y da su consentimiento. Tu app nunca ve su contraseña.
El servidor redirige de vuelta a la
redirect_uricon uncode, un código de autorización de corta duración.Tu backend canjea el código en el endpoint de token enviando el
codey elcode_verifieroriginal.El servidor recalcula la huella del verificador y la compara con el
code_challengedel paso 2. Si coinciden, entrega el token de acceso (y, si procede, un refresh token). Si alguien interceptó el código en el paso 4, no puede canjearlo porque no tiene el verificador.Tu app llama a la API con el token de acceso en la cabecera
Authorization.
No hemos ejecutado este flujo contra un proveedor real, porque exigiría registrar una aplicación con credenciales. Lo que sí hemos comprobado es el cálculo del paso 1, contrastándolo con el vector de prueba que publica el propio RFC 7636 en su apéndice B:
Salida real:
En la práctica casi nadie escribe esto a mano: las librerías cliente de OAuth y los servicios de identidad lo hacen por ti. Pero saber qué ocurre en cada paso es lo que te permite leer un error de invalid_grant y entender en dos minutos que el verificador no coincide con el reto.
Errores frecuentes al implementar autenticación
Los errores que más se repiten al implementar autenticación no vienen de no saber qué es un JWT, sino de detalles de configuración que los estándares ya avisan. Esta es la lista que repasaría antes de poner en producción cualquier login, con la fuente de cada punto:
Aceptar cualquier algoritmo al verificar un JWT. Fija la lista (
algorithms=["HS256"]o el que uses). Es la defensa contraalg: noney contra la confusión de claves que describe OWASP.Usar una contraseña como clave de firma. OWASP pide para HS256 una clave aleatoria de al menos 256 bits, distinta para cada emisor y audiencia, y nunca publicada.
No validar
exp,issyaud. Un token bien firmado pero emitido para otra API no debería abrirte la tuya.Guardar tokens en
localStorage. Va en contra de la guía de sesiones de OWASP; la alternativa es una cookie HttpOnly, Secure y SameSite, o un backend que guarde los tokens (el patrón BFF).No regenerar el identificador de sesión tras el login, lo que abre la puerta a la fijación de sesión.
Seguir usando el flujo implícito de OAuth. El RFC 9700 desaconseja el
response_type=tokenporque expone el token de acceso en la redirección. Usa código de autorización con PKCE.Pedir al usuario su contraseña dentro de tu app para obtener un token de otro servicio. Es el flujo de credenciales de contraseña del propietario, y el RFC 9700 dice que no debe usarse.
Comparar la
redirect_uricon reglas flexibles. El RFC 9700 exige comparación exacta de cadenas en el servidor de autorización.
Si tu app expone una API, estos puntos encajan con lo que contamos en la guía para proteger tus APIs frente a las vulnerabilidades más comunes. Y si todavía estás asentando cómo se diseña una API, empieza por qué es RESTful y sus seis principios: la autenticación se apoya en ellos.
Siguiente paso: implementar el login de un SaaS real
Entender sesiones, JWT y OAuth en un ejemplo de veinte líneas es el primer paso; el trabajo real empieza cuando tienes que unirlos con una base de datos, un frontend y un sistema de pagos sin dejar huecos. Ahí es donde la teoría de este artículo se convierte en criterio propio.
Es justo lo que se trabaja en Rock the AI Code, el máster Full Stack Developer con IA de ThePower Tech School. Según su ficha, consultada el 10-11-2026, el Proyecto 05, «Suscripción de pago», consiste en un gestor de enlaces con plan gratuito y premium en el que «implementa autenticación, pagos recurrentes y persistencia NoSQL con MongoDB para un producto SaaS». En el bloque de herramientas, «Datos y autenticación» incluye «JWT · OAuth · WebSockets», y entre lo que serás capaz de hacer al terminar figura el «login de usuarios con JWT, OAuth y Clerk, y cobros con Stripe y webhooks».
Si después quieres llevar ese login a producción con despliegues automáticos, el siguiente tema natural es CI/CD explicado, del commit a producción. Y si aún estás situando qué parte del trabajo cae en el servidor, repasa frontend y backend explicado fácil.
Preguntas frecuentes sobre sesiones, JWT y OAuth
¿Qué es JWT y para qué sirve?
JWT (JSON Web Token) es un formato de token definido en el RFC 7519 que transporta afirmaciones, como quién es el usuario o cuándo caduca el token, firmadas para que nadie pueda alterarlas. Sirve para que un servicio verifique la identidad o los permisos de quien llama sin consultar una base de datos central, por ejemplo en APIs protegidas con OAuth.
¿JWT es más seguro que una sesión con cookie?
No por sí mismo. Las dos opciones son seguras si se configuran bien y peligrosas si no. La sesión tiene una ventaja práctica: se puede invalidar al momento borrándola en el servidor. Un JWT sigue siendo válido hasta que caduca, salvo que mantengas una lista de revocados. Para una app web con backend propio, OWASP sugiere considerar sesiones normales.
¿OAuth y JWT son lo mismo?
No. OAuth 2.0 es un protocolo de autorización: define cómo una aplicación obtiene permiso y un token para acceder a datos en tu nombre. JWT es un formato de token. Se combinan a menudo, porque muchos servidores emiten sus tokens de acceso como JWT y OpenID Connect usa un JWT como ID token, pero OAuth no obliga a usar JWT.
¿Dónde guardo un JWT en el navegador?
En una cookie con los atributos HttpOnly, Secure y SameSite, o directamente en el servidor mediante un backend intermedio que guarde los tokens. OWASP desaconseja localStorage y sessionStorage para cualquier credencial, porque cualquier script de la página puede leerlos y un solo fallo de XSS bastaría para robar todos los tokens.
¿Se puede invalidar un JWT antes de que caduque?
No de forma nativa: se acepta mientras la firma y el claim exp sean válidos. Para invalidarlo antes necesitas una lista de tokens revocados que se consulte en cada petición, y eso devuelve estado al servidor. Por eso conviene que los tokens de acceso caduquen pronto y se renueven con un refresh token, que el servidor de autorización sí puede revocar.








