{"record":{"id":"9ac62e2040505fff","repo":"PrefectHQ/fastmcp","slug":"token-has-expired","errorCode":null,"errorMessage":"Token has expired","messagePattern":"Token has expired","errorType":"validation","errorClass":"JoseError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/jwt_issuer.py","lineNumber":277,"sourceCode":"\n            # Validate token type\n            token_use = payload.get(\"token_use\", \"access\")\n            if token_use != expected_token_use:\n                logger.debug(\n                    \"Token type mismatch: expected %s, got %s\",\n                    expected_token_use,\n                    token_use,\n                )\n                raise JoseError(\n                    f\"Token type mismatch: expected {expected_token_use}, \"\n                    f\"got {token_use}\"\n                )\n\n            # Validate expiration\n            exp = payload.get(\"exp\")\n            if exp is not None and exp < time.time():\n                logger.debug(\"Token expired\")\n                raise JoseError(\"Token has expired\")\n\n            # Validate issuer\n            if payload.get(\"iss\") != self.issuer:\n                logger.debug(\"Token has invalid issuer\")\n                raise JoseError(\"Invalid token issuer\")\n\n            # Validate audience\n            if payload.get(\"aud\") != self.audience:\n                logger.debug(\"Token has invalid audience\")\n                raise JoseError(\"Invalid token audience\")\n\n            logger.debug(\n                \"Token verified successfully for subject=%s\", payload.get(\"sub\")\n            )\n            return payload\n\n        except JoseError as e:\n            logger.debug(\"Token validation failed: %s\", e)","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/jwt_issuer.py#L259-L295","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Refresh the token before it expires (use the refresh token to mint a new one) and retry the request","If tokens keep expiring prematurely, check clock sync (NTP) between issuing and verifying servers","Fix client token caching to honor exp and refresh proactively (e.g. at 80% of lifetime)","Ensure a long enough lifetime at issuance if the workload legitimately needs longer-lived tokens"],"exampleFix":"// before\nuse(token)  # stale cached token\n// after\nif time.time() >= decoded[\"exp\"]:\n    token = refresh_access_token()\nuse(token)","handlingStrategy":"try-catch","validationCode":"claims = jwt.decode(token, options={\"verify_signature\": False})\nif (exp := claims.get(\"exp\")) is not None and exp < time.time():\n    token = refresh_access_token()  # refresh before calling verify_token","typeGuard":"def is_token_expired(claims: dict) -> bool:\n    exp = claims.get(\"exp\")\n    return exp is not None and exp < time.time()","tryCatchPattern":"try:\n    payload = issuer.verify_token(token)\nexcept JoseError as e:\n    if \"expired\" in str(e).lower():\n        token = refresh_access_token()\n        payload = issuer.verify_token(token)\n    else:\n        raise","preventionTips":["Refresh tokens proactively (e.g. at 80% of lifetime) rather than on failure","Keep token lifetimes appropriate to the operation duration","Synchronize clocks across servers with NTP"],"tags":["jwt","auth","token-expired"],"backgroundTag":"jwt-token-expired","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}