Cómo elegir una estrategia de autenticación para una nueva aplicación web
Elige la autenticación de una web app en 2026: sesiones o JWT, OAuth 2.0/OIDC, passkeys y proveedores gestionados, con revocación, CSRF y UX.
Para una nueva aplicación web en 2026, la estrategia se ajusta a la naturaleza de tu aplicación: un monolito con renderizado en servidor debería comenzar con sesiones del lado del servidor en una cookie firmada y HttpOnly; una SPA, cliente móvil o backend de API debería utilizar tokens de acceso de corta duración con rotación de refresh tokens almacenados en cookies HttpOnly; cualquier aplicación que necesite inicio de sesión social o SSO empresarial debería añadir OAuth 2.0/OIDC como capa adicional; y si la autenticación no es el núcleo de tu producto, la opción más sólida por defecto es un proveedor gestionado. Añade passkeys como factor resistente al phishing independientemente de la base que elijas. El resto de este artículo justifica cada rama frente a criterios concretos: revocación, escalabilidad, exposición a CSRF/XSS, complejidad y los modos de fallo en la experiencia de usuario que solo aparecen en producción.
La trampa en la que caen la mayoría de las nuevas aplicaciones es recurrir a JWTs porque un tutorial los usaba, sin tener una razón de escalabilidad sin estado que justifique el coste de revocación. Esta es una decisión comparativa, no una opción por defecto; por eso a continuación se presentan los criterios, una tabla comparativa y una matriz que relaciona el arquetipo de aplicación con una recomendación.
Puntos clave
- Sesiones frente a tokens es una cuestión de dónde reside el estado de autenticación — en el servidor o en el cliente — mientras que las cookies frente al encabezado
Authorizationsolo determinan cómo viaja la credencial; confundir ambos conceptos es la razón más común por la que nuevas aplicaciones adoptan JWTs sin necesitarlos. - Almacenar JWTs en
localStoragees ahora un antipatrón: cualquier vulnerabilidad XSS en tu página puede leer y exfiltrar el token, por lo que las credenciales deben mantenerse en cookies HttpOnly a las que la capa JavaScript no pueda acceder. - El problema difícil con los JWTs no es emitirlos, sino revocarlos; el cierre de sesión inmediato requiere tokens de corta duración con rotación de refresh tokens o una lista de denegación del lado del servidor, lo que silenciosamente reintroduce el estado que los JWTs pretendían eliminar.
- OAuth 2.1 sigue siendo un Internet-Draft del IETF (draft-15, marzo de 2026), no un RFC ratificado, pero su dirección está definida: PKCE obligatorio, comparación exacta de redirect URIs y eliminación de los flujos Implicit y de credenciales de contraseña.
- Si la autenticación no es tu producto, un proveedor gestionado (Auth0, Clerk, Supabase Auth, WorkOS, Stytch) es la opción pragmática por defecto en 2026 — heredas sesiones, inicio de sesión social, MFA y passkeys sin asumir la superficie de exposición ante brechas de seguridad.
El enfoque que elimina el ruido: dónde reside el estado frente a cómo viaja
Sesiones frente a tokens es una cuestión de dónde reside tu estado de autenticación — en el servidor o dentro de la credencial del cliente — mientras que las cookies frente al encabezado Authorization solo determinan cómo viaja esa credencial a través de HTTP. Son ejes ortogonales, y tratarlos como una única decisión es la razón por la que tantos equipos adoptan JWTs que no necesitan. Esta distinción es el hilo conductor de todo lo que se expone a continuación, y el proveedor de autenticación Authgear lo plantea de la misma manera.
Una sesión es con estado (stateful): el servidor almacena el registro autoritativo y entrega al cliente un ID opaco que apunta a él — el modelo de la tarjeta llave del hotel, donde la recepción mantiene la lista maestra. Un JWT es sin estado (stateless): las afirmaciones (claims) son autocontenidas y firmadas, por lo que cualquier servidor puede verificarlas sin necesidad de una consulta — el modelo del pasaporte digital, donde cualquier agente confía en la firma sin llamar al emisor. Cualquiera de los dos puede transmitirse tanto por una cookie como por el encabezado Authorization. Las cookies y los encabezados son transporte; las sesiones y los tokens son arquitectura.
| Criterio | Sesiones del lado del servidor | JWT / tokens autocontenidos |
|---|---|---|
| Dónde reside el estado | Servidor (almacén) | Cliente (token firmado) |
| Revocación | Inmediata — eliminar el registro | Difícil — TTL corto + rotación, o lista de denegación |
| Escalabilidad | Requiere almacén compartido (p. ej., Redis) | Sin estado; sin consulta por petición |
| Tamaño de la credencial | ~32 bytes de ID opaco | A menudo más de 800 bytes de claims por petición |
| Superficie de ataque principal | CSRF (las cookies se envían automáticamente) | XSS (robo del token desde almacenamiento legible por JS) |
| Mejor uso | Monolito con renderizado en servidor | API-first, SPA, móvil, microservicios |
El contraste de tamaño resume en una línea el intercambio entre sin estado y con estado: una cookie de sesión transporta aproximadamente 32 bytes de ID opaco y requiere una consulta al servidor, mientras que un JWT transporta los claims de forma inline — a menudo más de 800 bytes en cada petición — y omite la consulta. Se está intercambiando ancho de banda y latencia de revocación por la eliminación de un almacén.
Sesiones: la opción por defecto para aplicaciones con renderizado en servidor
Discover how at OpenReplay.com.
Para un monolito con renderizado en servidor, comienza con sesiones del lado del servidor en una cookie firmada, HttpOnly, Secure y SameSite — es la opción correcta más sencilla y te proporciona revocación instantánea de forma gratuita. El navegador envía la cookie automáticamente, no almacenas ningún estado de autenticación en JavaScript, y cerrar la sesión de un usuario es una única operación de eliminación en el servidor.
La mecánica consta de cuatro pasos: el usuario envía sus credenciales, el servidor crea un registro de sesión y devuelve su ID en una cookie, el navegador reenvía esa cookie en cada petición, y el cierre de sesión destruye ambos lados. Hashea las contraseñas con Argon2 antes de que lleguen al almacén — la OWASP Password Storage Cheat Sheet lo trata como el algoritmo de primera elección actualmente. Genera IDs de sesión con alta entropía y nunca incluyas datos sensibles en ellos; la OWASP Session Management Cheat Sheet es la referencia canónica para la longitud del ID, la rotación y el tiempo de expiración.
# FastAPI: issue an opaque session, store it server-side, set a hardened cookie
import secrets
from fastapi import FastAPI, Response
# redis client + Argon2 password verification omitted for brevity
app = FastAPI()
SESSION_TTL = 60 * 60 * 24 # 24 hours
@app.post("/login")
async def login(response: Response): # verify Argon2 hash first
session_id = secrets.token_urlsafe(32) # ~256 bits of entropy
await redis.setex(f"sess:{session_id}", SESSION_TTL, user_id)
response.set_cookie(
"session", session_id,
httponly=True, # JS cannot read it -> blunts XSS exfiltration
secure=True, # HTTPS only
samesite="lax", # mitigates CSRF on top-level navigations
max_age=SESSION_TTL,
)
return {"ok": True}
Este fragmento muestra el propósito fundamental de las sesiones: el secreto nunca reside en JavaScript, y el servidor mantiene la fuente de verdad. De aquí se derivan dos compromisos. Primero, la escalabilidad: dado que el registro está en el servidor, un despliegue con múltiples instancias necesita sesiones adhesivas (sticky sessions) o — mejor aún — un almacén compartido como Redis para que cualquier instancia pueda resolver cualquier sesión. Segundo, el CSRF: las cookies se envían automáticamente en peticiones entre sitios, por lo que combina SameSite con un token CSRF usando comparación en tiempo constante, según la OWASP CSRF Cheat Sheet. El indicador HttpOnly cierra la vía de lectura por XSS; SameSite junto con un token cierra la vía de CSRF.
JWTs y tokens: escalabilidad sin estado con un coste de revocación
Recurre a JWTs sin procesar cuando tengas una razón concreta de escalabilidad sin estado — una API que sirve a múltiples tipos de clientes, microservicios que transmiten identidad entre sí, o acceso entre dominios donde las cookies resultan incómodas — no como opción por defecto. La ventaja es real: un token firmado se verifica localmente contra una clave pública, por lo que se omite la consulta al almacén por cada petición y cualquier nodo puede autenticar cualquier solicitud.
El problema difícil con los JWTs no es emitirlos, sino revocarlos. Dado que el token es autocontenido, permanece válido hasta que expira independientemente de lo que piense tu servidor. El cierre de sesión inmediato requiere, por tanto, tokens de corta duración con rotación de refresh tokens, o una lista de denegación del lado del servidor que se comprueba en cada petición — y esa lista de denegación reintroduce silenciosamente el estado exacto que los JWTs pretendían eliminar. El patrón estándar es un token de acceso de corta duración combinado con un refresh token de mayor duración y rotación; la OAuth 2.0 Security Best Current Practice (RFC 9700, enero de 2025) es la referencia ratificada para la rotación de refresh tokens y los tiempos de vida cortos de los tokens de acceso — una fuente más sólida que los TTLs de números redondos que circulan en artículos de blog.
El lugar donde almacenas el token determina el radio de impacto en caso de brecha. Almacenar JWTs en localStorage es ahora un antipatrón: cualquier vulnerabilidad XSS en tu página puede leerlo y exfiltrar el token, por lo que mantén los tokens en cookies HttpOnly a las que la capa JavaScript no pueda acceder. Para aplicaciones en el navegador específicamente, el borrador de buenas prácticas actuales del IETF OAuth for Browser-Based Apps recomienda el flujo Authorization Code con PKCE y advierte que el JavaScript malicioso puede hacer mucho más que leer un token.
# FastAPI: verify an access token with PyJWT (not python-jose)
import jwt # PyJWT
from datetime import datetime, timezone
from fastapi import HTTPException
def verify_access_token(token: str, public_key: str) -> dict:
try:
return jwt.decode(
token, public_key,
algorithms=["RS256"], # never accept "none"; pin the alg
options={"require": ["exp", "iat", "sub"]},
)
except jwt.ExpiredSignatureError:
raise HTTPException(401, "token expired") # client should refresh
except jwt.InvalidTokenError:
raise HTTPException(401, "invalid token")
# When you mint tokens, use timezone-aware UTC:
# exp = datetime.now(timezone.utc) + timedelta(minutes=15)
Dos notas de versión mantienen esto actualizado. Usa PyJWT — el tutorial oficial de seguridad de FastAPI migró a él (import jwt) junto con pwdlib, y python-jose ahora solo recibe mantenimiento mínimo. Y genera las fechas de expiración con datetime.now(timezone.utc): datetime.utcnow() quedó obsoleto en Python 3.12 y, según la documentación de eliminaciones pendientes de Python, permanece obsoleto sin una versión de eliminación programada — sigue funcionando, pero la llamada con zona horaria explícita es el reemplazo recomendado.
OAuth 2.0/2.1 y OIDC: identidad delegada cuando la necesitas
Usa OAuth 2.0 con OpenID Connect cuando quieras que los usuarios inicien sesión con un proveedor de identidad existente — «Iniciar sesión con Google», GitHub, Microsoft, Okta — o cuando necesites SSO empresarial. Este es el modelo de la lista VIP: no verificas al usuario tú mismo; confías en un emisor que ya lo conoce. OAuth 2.0 gestiona la autorización delegada (tokens de acceso para APIs), y OpenID Connect añade una capa de token de identidad para que puedas saber quién es el usuario.
Vale la pena asumir la complejidad solo cuando la delegación o el SSO son un requisito genuino, porque el flujo incorpora partes móviles reales — códigos de autorización, PKCE, scopes, redirect URIs y rotación de claves JWKS. Para un MVP con inicio de sesión propio y sin requisito de inicio de sesión social, OAuth es excesivo; cargarás con la maquinaria de redirección sin aprovechar la delegación para la que existe.
Si lo adoptas, apunta al perfil moderno. OAuth 2.1 sigue siendo un Internet-Draft del IETF (draft-15, publicado el 2 de marzo de 2026), no un RFC ratificado, pero su dirección está definida. Las principales diferencias respecto a OAuth 2.0 son que PKCE es obligatorio para todos los clientes que usan el flujo de código de autorización, las redirect URIs deben compararse mediante coincidencia exacta de cadenas, y los flujos Implicit y Resource Owner Password Credentials han sido eliminados. El propio IETF señala que los Internet-Drafts son válidos durante un máximo de seis meses y que no es apropiado citarlos salvo como «trabajo en progreso», por lo que trata OAuth 2.1 como la consolidación objetivo — ya aplicada en la práctica por los principales proveedores de identidad — en lugar de un estándar publicado. Lo que importa es el fondo: usa Authorization Code + PKCE para todos los clientes, incluidos los confidenciales del lado del servidor, y elimina las redirect URIs con comodines.
Passkeys y WebAuthn: la dirección resistente al phishing
Añade passkeys como factor resistente al phishing, no como dogma. Basadas en WebAuthn/FIDO2, una passkey es un par de claves pública/privada donde la clave privada permanece en el hardware seguro del dispositivo y firma un desafío del servidor tras una verificación biométrica local o un PIN. Dado que la credencial está vinculada al origen y nunca se transmite, resiste el phishing, la repetición de ataques y el relleno de credenciales de una manera que las contraseñas y los códigos SMS no pueden.
El panorama normativo ha cambiado de forma decisiva. El NCSC anunció en abril de 2026 que las passkeys deberían ser ahora la primera opción de inicio de sesión para los consumidores en todos los servicios digitales, y que comenzará a recomendarlas allí donde un servicio las admita, y la verificación en dos pasos donde no las admita — un cambio que se refleja a través de una actualización continua de las directrices más que en un cambio repentino y único. La compatibilidad es prácticamente universal: las passkeys son compatibles con los principales sistemas operativos, incluyendo Windows, ChromeOS, macOS, Android, iOS y muchas distribuciones de Linux, así como con navegadores como Chrome, Firefox, Edge y Safari.
El soporte de passkeys está resuelto; el registro de passkeys no — y esa es la restricción de selección real. Según el Passkey Benchmark 2026 de Corbado (un benchmark de proveedor, no una estadística de población neutral), el registro web en el primer intento ronda el 49–83% en iOS pero solo el 25–39% en Windows. Trata la experiencia de usuario del registro y la recuperación de cuenta como problemas de diseño de primer nivel: ofrece las passkeys como una mejora opcional con una alternativa clara, y diseña el flujo de recuperación antes del lanzamiento, porque un dispositivo perdido sin vía de recuperación equivale a un usuario bloqueado.
Auth-as-a-Service: la opción pragmática por defecto cuando la autenticación no es tu producto
Si la autenticación no es tu producto, la opción por defecto más sólida en 2026 es un proveedor gestionado — Auth0, Clerk, Supabase Auth, WorkOS o Stytch — porque heredas sesiones, inicio de sesión social, MFA y passkeys sin asumir la superficie de exposición ante brechas ni el mantenimiento. Estos proveedores ofrecen SDKs listos para integrar que gestionan el ciclo de vida completo, incluida la maquinaria de redirección OAuth y las ceremonias WebAuthn que de otro modo tendrías que construir y depurar tú mismo.
La decisión es puramente económica. Desarrollar tu propia solución te da control y cero coste por MAU; un proveedor gestionado te da una implementación robusta, un tiempo de puesta en marcha más rápido y la guardia de seguridad de otro equipo para la ruta crítica de seguridad. Para la mayoría de las aplicaciones desde cero donde el inicio de sesión es un requisito básico y no un diferenciador, la ruta gestionada es el punto de partida racional — y siempre puedes migrar si la escala o el coste lo exigen.
Una matriz de decisión para los métodos de autenticación
La estrategia sigue el arquetipo de la aplicación. Identifica tu caso en la fila correspondiente, comienza por ahí y añade passkeys en todos los casos como factor resistente al phishing.
| Arquetipo de aplicación | Comienza con | Añade cuando sea necesario |
|---|---|---|
| Monolito con renderizado en servidor | Sesiones del lado del servidor (cookie HttpOnly) | OIDC para inicio de sesión social; passkeys como opción |
| SPA / móvil / backend de API | Tokens de acceso de corta duración + rotación de refresh, en cookies HttpOnly | OIDC para identidad de terceros |
| Necesita inicio de sesión social o SSO empresarial | OAuth 2.0 + OIDC (apuntando al perfil 2.1) | Sesiones o tokens para tus propias superficies |
| La autenticación no es tu producto | Proveedor gestionado (Auth0/Clerk/Supabase/WorkOS/Stytch) | — ya incluye el resto |
| Alta seguridad / orientado al consumidor | Cualquiera de las bases anteriores + passkeys | Flujo de recuperación diseñado desde el principio |
La arquitectura de producción habitual es híbrida: sesiones para la aplicación web con renderizado en servidor, JWTs para la API consumida por SPAs y clientes móviles, OIDC para el inicio de sesión social y passkeys como mejora opcional. La tendencia del sector que subyace a todo esto es enviar la contraseña menos veces, en menos lugares, con más alcance y responsabilidad — que es exactamente por qué los tokens con ámbito reemplazaron la autenticación Git basada en contraseñas y por qué las passkeys están desplazando las contraseñas por completo.
Lo que la repetición de sesiones revela sobre los modos de fallo en la UX de autenticación
La repetición de sesiones (session replay) es una técnica que reconstruye el flujo real del lado del cliente, y reproducir los recorridos de autenticación pone de manifiesto un catálogo reconocible de modos de fallo que los registros del backend no capturan — porque elegir la estrategia correcta sobre el papel no garantiza que los usuarios puedan realmente iniciar sesión (estas son capacidades de la técnica, no tasas de incidentes medidas):
- Bucles de cierre de sesión silencioso por expiración del token. Cuando un refresh token expira en mitad de una sesión, una SPA puede redirigir al usuario al inicio de sesión repetidamente. La repetición muestra el bucle de redirección en la línea de tiempo, algo que los registros de errores no capturan porque cada petición individual devuelve un 401 técnicamente válido.
- Registro de passkey abandonado. La repetición muestra dónde los usuarios abandonan la ceremonia WebAuthn — aparece el prompt del sistema operativo y el usuario lo cancela — que es exactamente el fallo que predicen los bajos porcentajes de registro en Windows.
- Callejones sin salida en la redirección OAuth. Una redirección post-consentimiento que lleva a una página en blanco o de error (redirect URI no coincidente, parámetro
stateperdido) es invisible en tus registros del backend porque el ciclo completo ocurre fuera de tu servidor; la repetición captura el callejón sin salida tal como lo vio el usuario. - Fallos de cookies específicos del navegador. Los problemas con
SameSitey cookies Secure que no se reproducen en local aparecen cuando la repetición correlaciona la sesión fallida con un navegador y versión específicos.
La conclusión es que la autenticación es una superficie de experiencia de usuario, no solo una elección de protocolo — y la estrategia que elijas determina cuál de estas clases de fallos estarás depurando.
Elige la base que se ajuste a la naturaleza de tu aplicación, refuerza el almacenamiento de credenciales (cookies HttpOnly, nunca localStorage) y añade passkeys como factor resistente al phishing de forma opcional. Para la mayoría de las nuevas aplicaciones, el primer paso honesto es un proveedor gestionado o un monolito con sesiones como punto de partida; recurre a JWTs sin procesar solo cuando un requisito real de escalabilidad sin estado justifique el coste de revocación. Una vez que el inicio de sesión esté en producción, observa el flujo real — la brecha entre una estrategia sólida y una que funciona es exactamente el detalle de experiencia de usuario que solo aparece en producción.
Preguntas frecuentes
¿Debería usar JWTs por defecto para una nueva aplicación web?
No. Usa JWTs sin procesar solo cuando tengas una razón concreta de escalabilidad sin estado, como una API que sirve a múltiples tipos de clientes o microservicios que transmiten identidad entre sí. Para un monolito con renderizado en servidor, las sesiones del lado del servidor son más sencillas y te proporcionan revocación instantánea. Los JWTs son autocontenidos, por lo que revocarlos antes de su expiración requiere tokens de corta duración con rotación de refresh tokens o una lista de denegación del lado del servidor, lo que reintroduce el estado exacto del servidor que los JWTs pretendían eliminar.
¿Es un token de sesión lo mismo que un JWT?
No. Un token de sesión es un ID opaco, de aproximadamente 32 bytes, que apunta al estado de autenticación almacenado en el servidor, por lo que cada petición requiere una consulta. Un JWT transporta sus claims de forma inline y se verifica localmente contra una firma sin necesidad de consulta, añadiendo a menudo más de 800 bytes por petición. Los dos representan el intercambio entre sin estado y con estado: una sesión mantiene el estado en el servidor, mientras que un JWT lo traslada a la credencial firmada del cliente.
¿Puedo almacenar un JWT en localStorage si sanitizo mis entradas?
No, almacenar un JWT en localStorage es un antipatrón independientemente de la sanitización de entradas, porque cualquier vulnerabilidad de cross-site scripting en cualquier parte de la página puede leer localStorage y exfiltrar el token. Mantén las credenciales en cookies HttpOnly, a las que JavaScript no puede acceder, y combínalas con SameSite y un token CSRF. Para aplicaciones en el navegador, el borrador de buenas prácticas actuales del IETF OAuth for Browser-Based Apps recomienda el flujo Authorization Code con PKCE en lugar del almacenamiento de tokens legible por el cliente.
¿Es OAuth excesivo para un MVP con inicio de sesión propio?
Sí, si tu MVP solo necesita inicio de sesión propio sin inicio de sesión social ni SSO empresarial, OAuth 2.0 añade maquinaria que no utilizarás. El flujo incorpora códigos de autorización, PKCE, scopes, redirect URIs y rotación de claves JWKS, todo lo cual existe para soportar la identidad delegada. Adopta OAuth con OpenID Connect solo cuando 'Iniciar sesión con Google', GitHub o el SSO empresarial sea un requisito genuino; de lo contrario, comienza con sesiones o un proveedor gestionado.