Cómo saber si un token JWT ha expirado (claim exp)

Por • 31 Octubre, 2026 • Seguridad • 12 minutos de lectura

Comprueba si un JWT ha expirado: lee el claim exp, míralo en tu zona horaria y evita los errores de milisegundos, leeway y base64url. Decodificador gratis.

Un token JWT ha expirado cuando la hora actual es igual o posterior a su claim exp. exp es un número de segundos contados desde el 1 de enero de 1970 en UTC: 1793372400 equivale al 30 de octubre de 2026 a las 15:00:00 UTC, que en Ciudad de México son las 9:00. Para comprobarlo, se decodifica la parte central del token, se lee exp y se compara con el reloj. Nuestro decodificador de JWT gratis lo hace por ti. Pega el token y verás Caducado o No ha caducado, junto con la fecha exacta en tu zona horaria y en UTC.

Es gratis, no necesitas cuenta y funciona por completo en tu navegador. En nuestra prueba del 31 de octubre de 2026, la página no envió ninguna petición que contuviera el token. Los visitantes tienen 5 usos de herramientas al día, y una cuenta gratuita de My Botsy (100 % gratis, sin spam) quita ese límite diario. Todos los resultados que citamos son reales. Las pruebas del decodificador se hicieron en mybotsy.com el 31 de octubre de 2026, y las de librerías ese mismo día con PyJWT 2.15.1, jsonwebtoken 9.0.3 y jose 6.2.14.

Qué significa el claim exp

El estándar de JWT, el RFC 7519, define tres claims de tiempo. Los tres son opcionales y los tres son de tipo NumericDate, es decir, segundos y no milisegundos:

  • exp (expiration time): a partir de ese instante, inclusive, el token no debe aceptarse.
  • nbf (not before): antes de ese instante, el token tampoco debe aceptarse.
  • iat (issued at): el momento en que se emitió. No invalida nada por sí solo, pero sirve para calcular la antigüedad del token.

Para exp y nbf, el RFC permite un pequeño margen, «normalmente no más de unos minutos», para compensar el desfase entre relojes. Por ese margen, dos sistemas pueden no ponerse de acuerdo sobre un token que caducó hace unos segundos.

Cómo saber si un token JWT ha expirado, paso a paso

  1. Copia el token. Es la cadena larga con dos puntos que sueles ver tras Authorization: Bearer en las cabeceras de una petición, o guardada en una cookie o en localStorage.
  2. Pégalo en el decodificador de JWT. No hace falta limpiarlo antes: la herramienta quita Authorization:, Bearer, las comillas, los espacios y los saltos de línea, y te avisa de que lo hizo.
  3. Mira la fila Estado. Indica Caducado, No ha caducado, Aún no es válido (si nbf está en el futuro), Sin caducidad (no tiene exp) o Caducidad ilegible: exp no es un número válido (si exp existe pero no es un número).
  4. Revisa la fila Caduca (exp). Verás la fecha en tu zona horaria, el mismo instante en UTC, un tiempo relativo («ayer», «dentro de 2 meses») y el número original.
  5. Fíjate en Duración. Es exp menos iat. Si un token de acceso dura 30 días, el problema está en cómo se emiten los tokens, no en este token en concreto.
  6. Lee los avisos. El decodificador advierte si las fechas están en milisegundos, si el algoritmo es none o si el token lleva relleno. También te recuerda que no comprueba la firma.

Ejemplos resueltos (salida real del decodificador)

Firmamos estos tokens de prueba con HS256 y un secreto de demostración desechable. Los pegamos en mybotsy.com/es/jwt-decoder el 31 de octubre de 2026, hacia las 15:07 UTC, con el navegador en la hora de Ciudad de México.

Ejemplo 1: un token caducado

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzg4NDEiLCJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJhdWQiOiJvcmRlcnMtYXBpIiwiaWF0IjoxNzkzMzY4ODAwLCJleHAiOjE3OTMzNzI0MDB9.FJV2oXvW0F-Q7x6rzZRKosQG_DhxLQXXz87wW-Ctq60
FilaResultado del decodificador
EstadoCaducado
Emitido (iat)viernes, 30 de octubre de 2026, 8:00:00 GMT-6 · 2026-10-30T14:00:00Z · ayer · 1793368800
Caduca (exp)viernes, 30 de octubre de 2026, 9:00:00 GMT-6 · 2026-10-30T15:00:00Z · ayer · 1793372400
Duración1 hora

Las tres librerías coincidieron. PyJWT lanzó ExpiredSignatureError: Signature has expired, jsonwebtoken lanzó TokenExpiredError: jwt expired con expiredAt 2026-10-30T15:00:00.000Z, y jose devolvió ERR_JWT_EXPIRED.

Ejemplo 2: un token válido pegado con su cabecera

Esta vez pegamos la línea completa: Authorization: Bearer eyJhbGciOiJIUzI1NiIs…. El decodificador avisó «Se limpió el texto antes de decodificarlo» y mostró No ha caducado, con exp el jueves, 31 de diciembre de 2026, 18:00:00 GMT-6, que es 2027-01-01T00:00:00Z («dentro de 2 meses»), y una Duración de 61 días 9 horas. Ojo con la fecha: un token que caduca a medianoche UTC de Año Nuevo deja de funcionar en México la tarde del 31 de diciembre. Mirar solo el número, o solo la fecha local, es la forma más habitual de equivocarse de día.

Ejemplo 3: aún no es válido

Un token con nbf 1796083200 y exp 1796169600 dio Aún no es válido, con Válido desde el lunes, 30 de noviembre de 2026, 18:00:00 GMT-6 (2026-12-01T00:00:00Z). PyJWT devolvió ImmatureSignatureError y jsonwebtoken NotBeforeError: jwt not active. Si tu servidor rechaza un token recién emitido, revisa nbf y la hora del servidor antes de culpar a la caducidad.

Ejemplo 4: sin exp

El payload {"sub":"svc-reporting","iat":1790856000} dio Sin caducidad (no tiene exp), y las tres librerías lo aceptaron. Si un token así se filtra, sirve hasta que se cambie la clave. En PyJWT, con options={"require": ["exp"]}, el mismo token pasó a dar MissingRequiredClaimError: Token is missing the "exp" claim.

Errores que dan una respuesta equivocada

exp en milisegundos

El fallo clásico es guardar Date.now() directamente en exp. Firmamos un token con "exp":1793462400000. El decodificador avisó «El valor de exp parece estar en milisegundos» lo leyó como milisegundos y mostró la fecha real, el 31 de octubre de 2026 a las 16:00 UTC, con el estado y la duración correctos. jsonwebtoken y jose lo aceptaron, así que en la práctica no caduca nunca. PyJWT lo rechazó, pero por su comprobación de iat («The token is not yet valid (iat)»), no por exp. La solución: divide entre 1000 al emitir el token y multiplica por 1000 al crear un Date de JavaScript a partir de exp.

exp como texto

"exp":"1798761600", con comillas, incumple el estándar. jsonwebtoken lo rechazó con invalid exp value y jose con "exp" claim must be a number. PyJWT lo aceptó y aun así lo aplicó: con una fecha ya pasada en texto lanzó ExpiredSignatureError. El resultado es que el mismo token funciona en un servicio y falla en otro. El decodificador muestra «No es una fecha válida: debe ser un número de segundos» junto al valor, un aviso, y la fila Estado dice Caducidad ilegible: exp no es un número válido.

Desfase de relojes y margen (leeway)

Creamos un token que había caducado 20 segundos antes. Sin margen, las tres librerías lo rechazaron. Con 30 segundos (leeway=30 en PyJWT, clockTolerance: 30 en jsonwebtoken y jose), las tres lo aceptaron. Mantén el margen pequeño: cada segundo que concedes alarga la vida de todos los tokens, incluidos los robados.

Decodificar con base64 --decode

Los JWT usan base64url, que cambia + y / por - y _. El típico comando cut -d. -f2 | base64 --decode | jq .exp funciona hasta que el payload contiene uno de esos caracteres. En GNU coreutils 9.7, un payload con - imprimió medio JSON y luego base64: invalid input, y jq falló con Unfinished string. Convierte primero los caracteres con tr '_-' '/+', o pega el token en el decodificador, que lee base64url de forma nativa. Para otros problemas con Base64, consulta cómo decodificar Base64.

Decodificar no es verificar

Cualquiera puede escribir un token con exp en el año 2100. Leer exp sirve para depurar o para que el navegador decida cuándo renovar el token. Antes de confiar en una petición, tu servidor debe verificar la firma con una lista fija de algoritmos permitidos. El RFC 8725 describe ataques en los que se cambió el algoritmo a none, y nuestro decodificador muestra un aviso en rojo si ve alg: none.

Comprobar la caducidad en código

Estas funciones sirven para mostrar la fecha o programar la renovación, no para la seguridad. Las probamos con los tokens de arriba: el caducado devolvió true, el válido y el de base64url devolvieron false, y los que no tenían exp o lo tenían como texto no devolvieron fecha.

// JavaScript (navegador o Node 16+)
function jwtExpiry(token) {
  const part = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
  const { exp } = JSON.parse(atob(part));
  if (typeof exp !== 'number') return null;   // sin exp, o exp como texto
  return new Date(exp * 1000);                 // segundos -> ms
}
function isExpired(token, leewaySeconds = 0) {
  const exp = jwtExpiry(token);
  return exp !== null && Date.now() >= exp.getTime() + leewaySeconds * 1000;
}
# Python 3
import base64, json, time

def jwt_exp(token):
    seg = token.split(".")[1]
    seg += "=" * (-len(seg) % 4)   # repone el relleno que el JWT elimina
    exp = json.loads(base64.urlsafe_b64decode(seg)).get("exp")
    return exp if isinstance(exp, (int, float)) else None

def is_expired(token, leeway=0):
    exp = jwt_exp(token)
    return exp is not None and time.time() >= exp + leeway

La comparación es «mayor o igual» porque el RFC dice que el token deja de valer en el instante exp, no después. En el servidor, usa la función de verificación de tu librería (jwt.decode con clave en PyJWT, jwt.verify en jsonwebtoken, jwtVerify en jose), que comprueba la firma y las fechas a la vez.

Buenas prácticas de seguridad

  • Trata un token vigente como una contraseña. Mientras no caduque, quien lo tenga puede actuar como ese usuario. Usa un decodificador local como el nuestro, en lugar de pegar tokens en chats o tickets.
  • Tokens de acceso de vida corta, y exige exp en el servidor.
  • Usa un secreto HS256 largo y aleatorio. PyJWT avisó de que nuestra clave de prueba de 20 bytes estaba «below the minimum recommended length of 32 bytes for SHA256». Un secreto de 64 caracteres del generador de contraseñas supera ese mínimo de sobra. Nuestra guía para crear una contraseña segura explica por qué importa la longitud.
  • No metas nada secreto en el payload. Solo está codificado, y cualquiera puede leerlo con un decodificador Base64. Si el payload decodificado no se puede leer como JSON, pásalo por el validador de JSON y consulta cómo arreglar un JSON no válido.

Los certificados también caducan. Si el error de «caducado» viene de HTTPS y no de un token, mira cómo comprobar la fecha de caducidad de un certificado SSL.

Comprueba tu token ahora

Pégalo en el decodificador de JWT gratis. Es gratis y no necesita cuenta. El token no sale de tu navegador, y verás el estado, todas las fechas en tu zona horaria y en UTC, y avisos para cada una de las trampas de arriba.

Preguntas frecuentes

¿Cómo sé cuándo caduca mi JWT?

Decodifica el payload y lee el claim exp, que cuenta los segundos desde el 1 de enero de 1970 en UTC. Multiplícalo por 1000 para obtener una fecha de JavaScript, o pega el token en el decodificador de JWT de My Botsy, que muestra exp en tu zona horaria y en UTC.

¿Un JWT caduca exactamente a la hora de exp?

Sí. El RFC 7519 dice que el token no debe aceptarse a partir de la hora de exp, inclusive, así que compara con mayor o igual. Muchos servidores añaden unos segundos de margen por el desfase de relojes.

¿Por qué el servidor rechaza un token que el decodificador da por vigente?

Las causas habituales son un reloj del servidor distinto, un nbf en el futuro, una audiencia o un emisor incorrectos, o una firma mala. El decodificador solo lee las fechas. El servidor además comprueba la firma y los demás claims.

¿Qué pasa si un JWT no tiene exp?

La mayoría de las librerías lo aceptan, así que el token no caduca nunca por sí solo. Exige exp en el servidor (por ejemplo, con la opción require en PyJWT) y usa tokens de acceso de vida corta.

¿Es seguro pegar un JWT en un decodificador online?

Solo si el decodificador funciona en local. El de My Botsy decodifica en tu navegador, y el token nunca se envía a nuestro servidor. Aun así, trata cualquier token vigente como una contraseña, y usa tokens caducados o de prueba cuando compartas ejemplos.

¿El decodificador de JWT de My Botsy es gratis?

Sí. Es gratis, no necesita cuenta y funciona en tu navegador. Los visitantes tienen 5 usos al día, y una cuenta gratuita elimina ese límite.

Cómo saber dónde se tomó una foto (ubicación GPS)

30 Octubre, 2026

Mira si una foto revela dónde se tomó: lee sus datos GPS en el navegador, ubica bien las coordenadas en el mapa y borra la ubicación antes d..

Cómo saber si una página web es confiable: 6 pasos gratis

29 Octubre, 2026

Comprueba si una web es confiable antes de pagar: dominio real, antigüedad con WHOIS, certificado SSL, redirecciones, hosting y Google Safe ..

Cómo crear una contraseña segura que puedas recordar

28 Octubre, 2026

Qué hace segura a una contraseña hoy, cómo armar una frase de contraseña fácil de recordar y cómo manejar el resto con un gestor y 2FA.