Definición
¿Alguna vez te has preguntado cómo un servidor de recursos sabe si ese Bearer token que acaba de recibir sigue siendo válido? Quizás expiró hace 5 minutos. Quizás el usuario lo revocó. Quizás nunca fue emitido por tu servidor de autorización. Token introspection resuelve exactamente este problema proporcionando una forma estandarizada de preguntar “Oye, ¿es este token legítimo?”
Token Introspection (RFC 7662) es una extensión de OAuth 2.0 que define un protocolo para que servidores de recursos o clientes consulten a un servidor de autorización sobre el estado actual de un token. En lugar de parsear y validar tokens localmente, haces una solicitud HTTP POST al endpoint de introspección y obtienes metadatos incluyendo si el token está activo, a quién pertenece, qué scopes tiene y cuándo expira.
Esto se vuelve crítico en sistemas distribuidos donde los tokens pueden ser revocados, los scopes pueden cambiar, o estás tratando con tokens opacos (tokens no-JWT que no contienen información legible). El endpoint de introspección es tu única fuente de verdad para la validez del token, proporcionando estado en tiempo real independientemente del formato del token.
Ejemplo
API Gateway de Stripe: Cuando llamas a la API de Stripe con un access token, su API gateway no solo confía ciegamente en el token. Introspecciona el token con su servidor de autorización para verificar que sigue activo, no ha sido revocado y tiene los scopes requeridos para la operación solicitada. Esto ocurre en cada solicitud para máxima seguridad.
Plataforma de API Corporativa: Una gran empresa tiene más de 50 microservicios. Cuando el Servicio A recibe una solicitud con un token, llama al endpoint de introspección del servidor OAuth de autorización central para verificar que el token está activo, pertenece al usuario reclamado y tiene el scope “read:users” necesario para la operación.
Integración de Terceros: La integración OAuth de GitHub con plataformas CI/CD. Cuando CircleCI o Travis CI recibe un webhook con un access token, lo introspecciona para asegurar que sigue siendo válido y que el usuario no ha revocado el acceso desde que se completó el flujo OAuth inicial.
Seguridad de API Bancaria: Una API bancaria recibe un token para una transferencia de dinero. Antes de procesar la transferencia, introspecciona el token para verificar que está activo (no revocado después de un reinicio de contraseña), sigue dentro de su período de validez y tiene el scope “transfer:funds” con límites de transacción apropiados.
Validación de Token en App Móvil: Un backend de app móvil recibe tokens de dispositivos potencialmente comprometidos. En lugar de confiar solo en firmas JWT, introspecciona cada token para detectar tokens que fueron revocados debido a actividad sospechosa, asegurando que tokens robados no puedan usarse incluso si no han expirado todavía.
Analogía
El Portero con un Teléfono: Imagina la validación JWT como un portero verificando tu tarjeta de identidad en la puerta - verifican que la tarjeta se vea real (firma), no haya expirado y coincida con tu cara. Token introspection es como el portero también llamando a la base de datos del gobierno para verificar que tu ID no fue reportada como robada, revocada o marcada. La tarjeta puede verse válida, pero solo la autoridad central conoce su estado actual.
La Verificación de Entrada de Concierto: Tienes una entrada impresa de concierto que se ve legítima. El verificador de entradas no solo la mira - la escanea en su sistema para verificar que no fue ya usada, no fue reembolsada y que el concierto realmente existe. Token introspection es esa verificación en tiempo real contra la base de datos central.
La Verificación de Tarjeta de Biblioteca: Tu tarjeta de biblioteca tiene una fecha de expiración impresa, pero cuando intentas sacar un libro, el bibliotecario la escanea en su sistema. ¿Por qué? Porque aunque la tarjeta diga “válida hasta 2027,” podrías tener multas sin pagar, una cuenta suspendida o haber excedido tu límite de préstamos. La tarjeta en sí no puede decirle eso al bibliotecario - solo el sistema central puede.
La Autorización de Tarjeta de Crédito: Cuando pasas tu tarjeta de crédito en una tienda, la tarjeta tiene tu nombre y número, pero el terminal no solo confía en eso. Contacta a tu banco en tiempo real para verificar que la tarjeta está activa, no reportada como robada, tiene fondos suficientes y la transacción se ajusta a patrones normales. Esa solicitud de autorización es exactamente lo que token introspection hace para tokens OAuth.
Ejemplo de Código
# Solicitud POST al endpoint de introspección
POST /oauth/introspect HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=b9e8e4f0a1b2c3d4e5f6g7h8i9j0k1l2&
token_type_hint=access_token
# Respuesta exitosa para token activo
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": true,
"scope": "read:users write:users read:orders",
"client_id": "s6BhdRkqt3",
"username": "jdoe",
"token_type": "Bearer",
"exp": 1672531200,
"iat": 1672527600,
"nbf": 1672527600,
"sub": "user-12345",
"aud": "https://api.example.com",
"iss": "https://auth.example.com",
"jti": "token-unique-id-abc123"
}
# Respuesta para token inactivo/revocado
HTTP/1.1 200 OK
Content-Type: application/json
{
"active": false
}
# Ejemplo: Servidor de recursos validando solicitud entrante
# Ejemplo Node.js/Express
const express = require('express');
const axios = require('axios');
app.get('/api/protected-resource', async (req, res) => {
// Extraer token del encabezado Authorization
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token faltante o inválido' });
}
const token = authHeader.substring(7); // Remover prefijo "Bearer "
try {
// Llamar al endpoint de introspección
const introspectionResponse = await axios.post(
'https://auth.example.com/oauth/introspect',
new URLSearchParams({
token: token,
token_type_hint: 'access_token'
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${Buffer.from(
`${CLIENT_ID}:${CLIENT_SECRET}`
).toString('base64')}`
}
}
);
const tokenInfo = introspectionResponse.data;
// Verificar si el token está activo
if (!tokenInfo.active) {
return res.status(401).json({
error: 'El token no está activo o ha sido revocado'
});
}
// Verificar expiración (enfoque de cinturón y tirantes)
if (tokenInfo.exp && tokenInfo.exp < Math.floor(Date.now() / 1000)) {
return res.status(401).json({ error: 'Token expirado' });
}
// Verificar scope requerido
const requiredScope = 'read:users';
if (!tokenInfo.scope || !tokenInfo.scope.includes(requiredScope)) {
return res.status(403).json({
error: `Scope insuficiente. Requerido: ${requiredScope}`
});
}
// El token es válido y tiene el scope requerido
// Proceder con la solicitud, adjuntar información del usuario al objeto de solicitud
req.user = {
id: tokenInfo.sub,
username: tokenInfo.username,
scopes: tokenInfo.scope.split(' ')
};
// Manejar la solicitud de recurso protegido
res.json({
message: 'Acceso concedido',
user: req.user
});
} catch (error) {
console.error('Introspección falló:', error);
return res.status(500).json({
error: 'Error al validar token'
});
}
});
Diagrama
sequenceDiagram
participant Client as Cliente
participant ResourceServer as Servidor de Recursos
participant AuthServer as Servidor de Autorización
participant Database as Base de Datos
Client->>ResourceServer: GET /api/data
Authorization: Bearer {token}
Note over ResourceServer: Extraer token del
encabezado Authorization
ResourceServer->>AuthServer: POST /introspect
token={token}
Authorization: Basic {credentials}
AuthServer->>Database: Consultar estado del token
Verificar lista de revocación
Verificar expiración
Database-->>AuthServer: Metadatos del token
alt Token está activo
AuthServer-->>ResourceServer: 200 OK
{"active": true, "scope": "...", ...}
Note over ResourceServer: Validar scopes
Verificar expiración
Verificar audiencia
ResourceServer->>ResourceServer: Procesar solicitud con
contexto de usuario validado
ResourceServer-->>Client: 200 OK
Datos de recurso protegido
else Token está inactivo/revocado
AuthServer-->>ResourceServer: 200 OK
{"active": false}
ResourceServer-->>Client: 401 Unauthorized
El token no está activo
end
Notas de Seguridad
CRÍTICO - …
Configuración y Validación:
- El endpoint de introspección de tokens DEBE estar protegido con autenticación de cliente (client_id + client_secret o mTLS) para prevenir que partes no autorizadas sondeen la validez de tokens.
- Solo servidores de recursos confiables deberían poder introspeccionar tokens.
- Implementar limitación de tasa en el endpoint de introspección para prevenir ataques de denegación de servicio de clientes maliciosos intentando validar números masivos de tokens.
- La respuesta DEBE siempre devolver HTTP 200 OK con “active”: false para tokens inválidos en lugar de códigos de error, para evitar filtrar información sobre la existencia del token.
- Nunca cachear respuestas de introspección para tokens activos por más de unos segundos, ya que el estado del token puede cambiar instantáneamente vía revocación.
Monitoreo y Protección:
- Validar siempre que el token_type_hint coincida con el tipo de token real para prevenir ataques de confusión.
- Proteger solicitudes de introspección con TLS/HTTPS en todo momento ya que los tokens se transmiten en el cuerpo de la solicitud.
- Registrar todas las solicitudes de introspección para auditoría de seguridad y detección de anomalías.
- Implementar listas blancas de IP para clientes de introspección cuando sea posible.
- Considerar el impacto de rendimiento de introspeccionar cada solicitud - usar caché estratégicamente pero cautelosamente.
- Para escenarios de alta seguridad, introspeccionar en cada solicitud a pesar de costos de rendimiento.
Mejores Prácticas
- Autenticar Clientes de Introspección: Requerir credenciales de cliente (Basic Auth, mTLS o JWT bearer) para acceder al endpoint de introspección
- Usar token_type_hint: Siempre proporcionar pista “access_token” o “refresh_token” para mejorar rendimiento de búsqueda
- Validar Claims Críticos: Verificar
active,exp,scopeyaud(audiencia) en la respuesta - Manejar Tokens Inactivos: Tratar
"active": falsecomo no autorizado (401) y no proceder más - Cachear Cautelosamente: Cachear resultados de introspección por máximo 5-30 segundos, y solo para tokens activos
- Limitar Tasa: Implementar limitación de tasa en lado cliente y servidor
- Monitorear Rendimiento: Introspección añade latencia - monitorear y optimizar rendimiento del endpoint
- Usar para Tokens Opacos: Introspección es esencial para tokens opacos (no-JWT) que no llevan metadatos
- Cinturón y Tirantes con JWT: Incluso con JWTs, introspeccionar para estado de revocación en tiempo real en escenarios de alta seguridad
- No Filtrar Información: Siempre devolver 200 OK con información mínima para tokens inválidos
Errores Comunes
Confiar Solo en Expiración JWT: Depender solo del claim exp de JWT sin introspección significa que tokens revocados permanecen válidos hasta la expiración. Solución: Introspeccionar para operaciones críticas.
Sin Autenticación de Cliente: Exponer el endpoint de introspección sin autenticación permite a atacantes sondear validez de tokens. Solución: Requerir credenciales de cliente.
Cachear Demasiado Tiempo: Cachear resultados de introspección por minutos u horas derrota el propósito de validación en tiempo real. Solución: Cachear por segundos, no minutos.
Filtrar Información: Devolver diferentes códigos de error para “expirado” vs “revocado” vs “inválido” filtra información a atacantes. Solución: Devolver 200 OK con "active": false para todos los tokens inválidos.
Suposiciones de Rendimiento: Introspeccionar en cada solicitud sin infraestructura apropiada puede crear cuellos de botella. Solución: Arquitectar para escala con caché y consultas rápidas de base de datos.
Ignorar token_type_hint: No proporcionar la pista fuerza al servidor a verificar todo el almacenamiento de tokens (acceso, refresco, etc.). Solución: Especificar siempre el tipo.
Sin Limitación de Tasa: Permitir solicitudes de introspección ilimitadas habilita ataques DoS. Solución: Implementar límites de tasa por cliente.