Guías JWT

Cómo depuro un error JWT 401 Unauthorized

Mi orden para errores JWT 401: Authorization header, exp/nbf, firma, issuer, audience, token type, scopes y logs del servidor.

Por JWTDecoder.toolsÚltima verificación 2026-09-16

Voz editorial: la primera persona describe el flujo de trabajo documentado de JWTDecoder.tools; JWTDecoder.tools es el editor y autor organizacional.

Flujo para depurar JWT 401
Compruebo header, tiempo, firma, issuer, audience, tipo de token y autorización.

Ante 401 Unauthorized con JWT no empiezo por scopes ni por editar claims. Recorro la cadena de autenticación desde fuera hacia dentro: HTTP header, tiempo, firma, issuer, audience, token type y solo después autorización.

Con Expected Issuer/Audience conocidos empiezo en JWT Debugger.

1. Confirmo el Authorization header

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Compruebo:

  • header ausente;
  • Bearer incorrecto;
  • token entre comillas;
  • reverse proxy elimina Authorization;
  • cliente reusa un token viejo;
  • ID Token enviado como Access Token.

Un JWT perfecto no sirve si nunca llega a la middleware que lo valida.

2. Leo WWW-Authenticate y logs

RFC 6750 define respuestas Bearer como:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"

Busco detalle adicional:

token expired
invalid signature
invalid issuer
invalid audience
kid not found
token not active

invalid_token solo no identifica la causa. Registro también request ID, ruta, método, issuer esperado, audience esperada y categoría del fallo, pero nunca el JWT completo ni secretos.

3. Reviso exp, nbf y reloj del servidor

JWT Expiry Checker me permite separar:

now < nbf   => todavía no activo
now >= exp  => expirado

También descarto seconds/milliseconds y clock skew. Comparo siempre con el reloj del servidor que realmente rechaza la petición, no con el del navegador.

4. Verifico firma y algoritmo

Decodificar no autentica. Compruebo:

  • algoritmo permitido;
  • secret/public key correcto;
  • kid;
  • JWKS y cache;
  • key rotation;
  • bytes originales header.payload.

Uso JWT Signature Verifier o JWKS Validator.

5. Comparo iss exactamente

expected: https://login.example.com/prod
actual:   https://login.example.com/dev

Si iss cambia, reviso también tenant, discovery y la fuente de claves. Una firma con una clave de otro entorno no convierte ese token en válido para producción.

6. Valido aud para la API concreta

{ "aud": ["api://billing", "api://reporting"] }

Si Orders espera api://orders, rechazo. La guía de audience mismatch cubre arrays, environments y confusión entre ID/Access Token.

7. Distingo 401 de 403

EstadoDiagnóstico inicial
401token/identidad no aceptados
403identidad aceptada, permiso insuficiente

La implementación concreta puede variar, pero esta separación evita perder tiempo buscando roles cuando firma o audience ni siquiera pasan.

8. Instrumento la middleware para saber dónde falla

En Node/Express prefiero registrar la etapa, no el token:

app.use(async (req, res, next) => {
  const auth = req.get("authorization");
  if (!auth?.startsWith("Bearer ")) {
    console.warn("auth_fail", { stage: "missing_bearer", requestId: req.id });
    return res.status(401).end();
  }

  try {
    req.auth = await verifyAccessToken(auth.slice(7));
    next();
  } catch (error) {
    console.warn("auth_fail", {
      stage: classifyJwtError(error),
      requestId: req.id,
    });
    res.status(401).end();
  }
});

Categorías útiles: missing_bearer, expired, not_active, bad_signature, unknown_kid, issuer_mismatch, audience_mismatch y wrong_token_type. Con eso puedo medir qué clase de 401 domina sin exponer credenciales.

9. Compruebo gateways y proxies con una comparación de headers

Si funciona localmente pero falla detrás de Nginx, CDN o API Gateway, comparo el request antes y después del proxy. Un ejemplo de configuración que preserva el header es:

proxy_set_header Authorization $http_authorization;
proxy_set_header X-Request-ID $request_id;

No asumo que todos los productos necesiten esta línea exacta; compruebo el comportamiento real del gateway. También descarto reglas que sobrescriben Authorization, validadores duplicados o audiences distintas entre edge y aplicación.

10. Reproduzco el fallo con curl

Un caso mínimo elimina ruido del frontend:

curl -i https://api.example.com/orders \
  -H 'Authorization: Bearer eyJ...'

Guardo status, WWW-Authenticate, request ID y hora UTC. Si el mismo token funciona contra otro endpoint, comparo audience, gateway y policy del recurso en vez de volver a emitir tokens al azar.

Qué métricas me ayudan en producción

No necesito almacenar JWTs para observar autenticación. Suelo contar:

  • 401 por ruta;
  • 401 por categoría de validación;
  • unknown_kid por issuer;
  • expirados vs firma inválida;
  • latencia/fallos al refrescar JWKS;
  • 401 antes y después de una rotación de claves.

Un pico de unknown_kid tras una rotación apunta a cache/JWKS. Un pico de audience_mismatch tras desplegar una API nueva apunta a adquisición/configuración del token.

Casos negativos que pruebo antes de desplegar

sin Authorization                 -> 401
Bearer vacío                       -> 401
token expirado                     -> 401
token nbf futuro                   -> 401
firma manipulada                   -> 401
issuer incorrecto                  -> 401
audience incorrecta                -> 401
access token válido sin permiso    -> 403 o policy equivalente

Estos tests protegen contra el peor “arreglo”: relajar validaciones hasta que el happy path pase.

Árbol de decisión

Authorization presente?
  no -> cliente/proxy
  sí
   ↓
tiempo válido?
  no -> refresh/clock
  sí
   ↓
firma válida con key confiable?
  no -> alg/kid/key/JWKS
  sí
   ↓
iss correcto?
  no -> issuer/tenant/environment
  sí
   ↓
aud correcto?
  no -> resource/token acquisition
  sí
   ↓
token type + permissions

Mi checklist de JWT 401

  1. Reproduzco request y guardo status, response headers, request ID y logs.
  2. Confirmo Authorization: Bearer al entrar en la API.
  3. Decodifico el token original con JWT Decoder.
  4. Compruebo exp, nbf y server clock.
  5. Verifico algoritmo y firma con key confiable.
  6. Comparo iss con la configuración.
  7. Comparo aud con la API esperada.
  8. Confirmo que envío Access Token, no ID Token.
  9. Comparo edge/gateway y aplicación si el fallo solo aparece detrás del proxy.
  10. Solo después reviso scopes/roles/policies.
  11. Corrijo la capa que falla en vez de desactivar validaciones.

Así convierto un 401 genérico en una secuencia reproducible de hipótesis comprobables.

Fuentes y referencias

Guías relacionadas