tutoriales·9 分で読める

OAuth 2.0 vs JWT: Cuándo Usar Cada Uno (y Cuándo Usarlos Juntos)

Entiende la diferencia real entre OAuth 2.0 y JWT, por qué no son lo mismo, y cómo combinarlos correctamente en autenticación moderna.

DevToolsHub Team
·
OAuth 2.0 vs JWT: Cuándo Usar Cada Uno (y Cuándo Usarlos Juntos)

La confusión más común en autenticación

Si preguntas a diez desarrolladores "¿cuál es la diferencia entre OAuth 2.0 y JWT?", probablemente obtendrás diez respuestas diferentes. Y muchas de ellas estarán parcialmente equivocadas.

La confusión es comprensible: los dos conceptos aparecen juntos constantemente. "Usamos OAuth con JWT". "El JWT es el token de OAuth". "Implementamos OAuth 2.0 con Bearer tokens JWT". La terminología se mezcla hasta que ya no sabes qué hace qué.

Vamos a separar esto de una vez por todas.


OAuth 2.0: un protocolo de autorización

OAuth 2.0 no es una tecnología, no es un formato de datos, y no es una librería. Es un protocolo: un conjunto de reglas que define cómo un sistema puede conceder acceso limitado a recursos en nombre de un usuario.

La pregunta que responde OAuth 2.0 es: "¿Puede esta aplicación acceder a estos recursos en nombre de este usuario?"

El flujo básico que todos conocen sin saberlo

Cuando haces clic en "Continuar con Google" en cualquier app, estás usando OAuth 2.0:

1. Tu app redirige al usuario → Google
2. Google pide permiso al usuario: "¿Permite que [App] acceda a tu email?"
3. El usuario acepta
4. Google redirige de vuelta a tu app con un código de autorización
5. Tu app intercambia ese código por un token de acceso
6. Tu app usa ese token para llamar a la API de Google

El token que recibes al final puede ser cualquier cosa: una cadena opaca, un UUID, o... un JWT. OAuth 2.0 no especifica el formato del token.

Los cuatro flujos (grant types) que debes conocer

OAuth 2.0 define diferentes flujos para diferentes escenarios:

Authorization Code (el más común, el del ejemplo de Google):

  • Para apps web y móviles con usuario humano
  • El código de autorización viaja por el navegador; el token de acceso por servidor
  • El más seguro para usuarios finales

Client Credentials:

  • Para comunicación servidor-a-servidor (machine-to-machine)
  • No hay usuario humano: tu microservicio A obtiene un token para hablar con el microservicio B
  • Equivale a "la aplicación se autentica, no el usuario"

Device Code:

  • Para dispositivos sin navegador (Smart TVs, CLI tools)
  • El dispositivo muestra un código que el usuario introduce en otro dispositivo con navegador

Refresh Token:

  • No es un flujo por sí solo, sino un mecanismo para obtener nuevos access tokens sin que el usuario vuelva a autenticarse
// Ejemplo simplificado: Client Credentials
const response = await fetch('https://auth.example.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'client_credentials',
    client_id: 'mi-microservicio',
    client_secret: process.env.CLIENT_SECRET,
    scope: 'read:orders write:shipments',
  }),
});

const { access_token, expires_in } = await response.json();
// access_token puede ser cualquier formato, incluyendo JWT

JWT: un formato de token

JWT (JSON Web Token) no es un protocolo de autenticación. Es un formato estándar para representar claims (afirmaciones) de forma segura entre dos partes.

La pregunta que responde JWT es: "¿Cómo empaqueto información de forma que el receptor pueda verificar que no ha sido manipulada?"

Ya vimos JWT en detalle en nuestro post anterior sobre JWT para desarrolladores. Pero el punto clave es este:

Un JWT es simplemente un JSON firmado digitalmente. Puede contener cualquier información:

{
  "sub": "user_12345",
  "email": "ana@ejemplo.com",
  "roles": ["admin", "editor"],
  "exp": 1718899200,
  "iat": 1718812800
}

Esta información está firmada con una clave secreta (HMAC) o un par de claves pública/privada (RSA/ECDSA). Cualquiera con la clave pública puede verificar que el token no fue modificado, sin consultar ninguna base de datos.


La relación real: OAuth 2.0 usa JWT como formato de token

Aquí está el secreto: OAuth 2.0 y JWT son complementarios, no alternativos.

OAuth 2.0 es el "qué" (el protocolo de autorización). JWT es el "cómo" (el formato del token que ese protocolo emite).

OAuth 2.0 Protocol
       │
       ▼
  Access Token ──────► puede ser un JWT
  Refresh Token ─────► generalmente NO es un JWT (cadena opaca)
  ID Token ───────────► SIEMPRE es un JWT (estándar OIDC)

Nota: el Refresh Token casi nunca debería ser un JWT. Un JWT es autocontenido y no se puede revocar fácilmente. El Refresh Token necesita poder revocarse, por lo que es mejor como referencia opaca en una base de datos.


OpenID Connect: la capa de identidad sobre OAuth

OAuth 2.0 resuelve autorización (acceso a recursos). Pero muchos lo usan para autenticación (¿quién es este usuario?), lo que no era su propósito original.

OpenID Connect (OIDC) es un protocolo de autenticación construido sobre OAuth 2.0. Añade:

  • Un ID Token (siempre JWT) que identifica al usuario
  • Un endpoint /userinfo estándar
  • Scopes específicos: openid, profile, email
OAuth 2.0 (autorización)
      │
      └── OpenID Connect (autenticación)
              │
              ├── Access Token (puede ser JWT)   → "accede a esta API"
              ├── Refresh Token (opaco)           → "renueva el access token"
              └── ID Token (siempre JWT)          → "este usuario es Ana García"

Cuando usas "Sign in with Google", en realidad usas OIDC (que usa OAuth 2.0). Google te da un ID Token JWT con la información del usuario.


Cuándo usar cada enfoque

Usa JWT como formato de token cuando:

  • Necesitas stateless authentication: el servidor no necesita consultar una base de datos para validar cada request
  • Tienes múltiples servicios que necesitan validar el mismo token (microservicios)
  • La información del token (roles, permisos) no cambia frecuentemente
  • Aceptas que no puedes revocar el token antes de su expiración (solo puedes esperar a que expire)
// Validación JWT stateless: sin base de datos
import jwt from 'jsonwebtoken';

function validateToken(token) {
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    // Si no lanza error, el token es válido y no fue manipulado
    return { valid: true, payload };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Usa tokens opacos (referencia a base de datos) cuando:

  • Necesitas revocar tokens inmediatamente (logout, cambio de contraseña, sospecha de compromiso)
  • Los permisos del usuario cambian frecuentemente y deben aplicarse de inmediato
  • Priorizas control sobre rendimiento
// Validación de token opaco: consulta a base de datos
async function validateOpaqueToken(token) {
  const session = await db.sessions.findOne({ 
    token,
    expires_at: { $gt: new Date() },
    revoked: false
  });
  
  if (!session) return { valid: false };
  
  const user = await db.users.findById(session.user_id);
  return { valid: true, user };
}

Usa OAuth 2.0 cuando:

  • Tu app necesita acceder a recursos de terceros en nombre del usuario (APIs de Google, GitHub, Stripe)
  • Quieres delegar autenticación a un proveedor de identidad (auth0.com, Okta, Keycloak)
  • Necesitas un estándar probado para emitir y gestionar tokens en sistemas complejos
  • Construyes una API pública que otras apps consumirán

No uses OAuth 2.0 cuando:

  • Tu app es simple (monolito, un solo servicio, tus propios usuarios)
  • Solo necesitas autenticación básica usuario/contraseña
  • El overhead de implementar un Authorization Server no se justifica

Para apps sencillas, una sesión con JWT firmado o incluso cookies de sesión es suficiente y más simple.


El patrón más común en producción

La arquitectura que ves en la mayoría de aplicaciones modernas combina todo:

Usuario → [Tu App] → Authorization Server (OAuth 2.0 + OIDC)
                          │
                          ├── ID Token (JWT)     → ¿Quién es el usuario?
                          ├── Access Token (JWT) → ¿Qué puede hacer?
                          └── Refresh Token (opaco) → Renovar sin re-login

[Tu App] → [Tu API] con Access Token JWT en header Authorization: Bearer
[Tu API] → valida JWT localmente (sin consultar Authorization Server)
// Frontend: guardar tokens tras login OAuth
async function handleOAuthCallback(code) {
  const tokens = await exchangeCodeForTokens(code);
  
  // ID Token: información del usuario
  const userInfo = decodeJWT(tokens.id_token);
  
  // Access Token: para llamar a APIs protegidas
  localStorage.setItem('access_token', tokens.access_token);
  
  // Refresh Token: para renovar (mejor en httpOnly cookie)
  // NO en localStorage
}

// Backend: validar access token en cada request
async function authenticatedRoute(req, res) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  const { valid, payload } = validateToken(token);
  if (!valid) return res.status(401).json({ error: 'Unauthorized' });
  
  // payload contiene: sub, roles, exp, etc.
  const user = payload;
  // ... lógica de negocio
}

Errores comunes

Error 1: Guardar el Refresh Token en localStorage

El Refresh Token tiene larga duración (días, semanas). Si un atacante lo obtiene, puede generar nuevos Access Tokens indefinidamente. Guárdalo siempre en una httpOnly cookie (no accesible desde JavaScript).

// ❌ Nunca
localStorage.setItem('refresh_token', refreshToken);

// ✅ Siempre (servidor)
res.cookie('refresh_token', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 días
});

Error 2: Usar el Access Token JWT para sesiones de larga duración

Los JWT son difíciles de revocar. Un Access Token debería tener corta duración (15 minutos a 1 hora). Si necesitas sesiones largas, usa Refresh Tokens para obtener nuevos Access Tokens.

Error 3: Poner información sensible en el JWT

El payload del JWT está codificado en Base64, no cifrado. Cualquiera puede decodificarlo. Usa nuestra herramienta JWT Decoder para comprobarlo tú mismo.

// ❌ Nunca en el payload JWT
{
  "credit_card": "4111-1111-1111-1111",
  "password_hash": "ef92b778..."
}

// ✅ Solo información no sensible
{
  "sub": "user_123",
  "roles": ["editor"],
  "email": "ana@ejemplo.com"
}

Error 4: Validar el JWT solo en el frontend

El frontend puede decodificar el JWT para mostrar información al usuario (nombre, avatar), pero la validación real de permisos debe ocurrir siempre en el servidor. Un usuario malicioso puede modificar el frontend.


Resumen: la tabla de decisión

Pregunta Respuesta Usa
¿Necesitas acceder a APIs de terceros? OAuth 2.0
¿Delegas autenticación a Google/GitHub? OAuth 2.0 + OIDC
¿Tienes microservicios que validan tokens? JWT como formato
¿Necesitas revocar tokens al instante? Tokens opacos + BD
¿App simple con usuarios propios? JWT directamente, sin OAuth
¿El token necesita expirar rápido? Access Token JWT (15-60 min)
¿El usuario puede estar semanas sin re-login? Refresh Token opaco + httpOnly cookie

OAuth 2.0 y JWT no compiten: uno es el protocolo que define cómo fluyen los tokens, el otro es el formato en que esos tokens se representan. Entender esta separación te permite combinarlos correctamente y elegir el nivel de complejidad que tu aplicación realmente necesita.

Practica decodificando JWTs con nuestra herramienta JWT Decoder para ver exactamente qué información contienen los tokens que recibes.

#oauth2#jwt#autenticación#seguridad#api

関連記事