PrefectHQ/fastmcp · error · JoseError

Token has expired

Error message

Token has expired

What it means

verify_token() checks the 'exp' (expiration, NumericDate) claim against the current time after verifying the signature. If exp exists and is in the past, the token is rejected with JoseError('Token has expired'). JWTs are short-lived by design; the verifier will not accept an expired token even with a valid signature.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/jwt_issuer.py:277

            # Validate token type
            token_use = payload.get("token_use", "access")
            if token_use != expected_token_use:
                logger.debug(
                    "Token type mismatch: expected %s, got %s",
                    expected_token_use,
                    token_use,
                )
                raise JoseError(
                    f"Token type mismatch: expected {expected_token_use}, "
                    f"got {token_use}"
                )

            # Validate expiration
            exp = payload.get("exp")
            if exp is not None and exp < time.time():
                logger.debug("Token expired")
                raise JoseError("Token has expired")

            # Validate issuer
            if payload.get("iss") != self.issuer:
                logger.debug("Token has invalid issuer")
                raise JoseError("Invalid token issuer")

            # Validate audience
            if payload.get("aud") != self.audience:
                logger.debug("Token has invalid audience")
                raise JoseError("Invalid token audience")

            logger.debug(
                "Token verified successfully for subject=%s", payload.get("sub")
            )
            return payload

        except JoseError as e:
            logger.debug("Token validation failed: %s", e)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Refresh the token before it expires (use the refresh token to mint a new one) and retry the request
  2. If tokens keep expiring prematurely, check clock sync (NTP) between issuing and verifying servers
  3. Fix client token caching to honor exp and refresh proactively (e.g. at 80% of lifetime)
  4. Ensure a long enough lifetime at issuance if the workload legitimately needs longer-lived tokens

Example fix

// before
use(token)  # stale cached token
// after
if time.time() >= decoded["exp"]:
    token = refresh_access_token()
use(token)
Defensive patterns

Strategy: try-catch

Validate before calling

claims = jwt.decode(token, options={"verify_signature": False})
if (exp := claims.get("exp")) is not None and exp < time.time():
    token = refresh_access_token()  # refresh before calling verify_token

Type guard

def is_token_expired(claims: dict) -> bool:
    exp = claims.get("exp")
    return exp is not None and exp < time.time()

Try / catch

try:
    payload = issuer.verify_token(token)
except JoseError as e:
    if "expired" in str(e).lower():
        token = refresh_access_token()
        payload = issuer.verify_token(token)
    else:
        raise

Prevention

When it happens

Trigger: Calling verify_token() with a JWT whose claims['exp'] < time.time() (jwt_issuer.py:277) — e.g. a cached token past its TTL, a long-lived test token, or a client that never refreshes.

Common situations: Client cached an access token and its TTL lapsed; server/client clock skew making a still-valid token look expired; replaying a captured/sample token from documentation or a test fixture; a refresh flow that fails so the app keeps sending the old token.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/9ac62e2040505fff. Report an issue: GitHub.