{"record":{"id":"eb8e6bcd2d238b68","repo":"quarkusio/quarkus","slug":"jwt-svid-from-spire-agent-is-already-expired","errorCode":null,"errorMessage":"JWT-SVID from SPIRE agent is already expired","messagePattern":"JWT-SVID from SPIRE agent is already expired","errorType":"exception","errorClass":"SpiffeConnectionException","httpStatus":null,"severity":"error","filePath":"extensions/spiffe-client/runtime/src/main/java/io/quarkus/spiffe/client/runtime/internal/SpiffeClientImpl.java","lineNumber":273,"sourceCode":"        }\n        if (!audience.containsAll(requestedAudiences)) {\n            throw new SpiffeConnectionException(\n                    \"JWT-SVID 'aud' claim does not contain the requested audiences; requested: \"\n                            + requestedAudiences + \", received: \" + audience);\n        }\n        if (audience.size() != requestedAudiences.size()) {\n            throw new SpiffeConnectionException(\n                    \"JWT-SVID 'aud' claim contains unexpected extra audiences; requested: \"\n                            + requestedAudiences + \", received: \" + audience);\n        }\n\n        Long exp = payload.getLong(\"exp\");\n        if (exp == null) {\n            throw new SpiffeConnectionException(\"JWT-SVID from SPIRE agent is missing the required 'exp' claim\");\n        }\n        Instant expiry = Instant.ofEpochSecond(exp);\n        if (expiry.isBefore(Instant.now())) {\n            throw new SpiffeConnectionException(\"JWT-SVID from SPIRE agent is already expired\");\n        }\n\n        record WorkloadJsonWebTokenImpl(String token, String subject, Set<String> audience,\n                Instant expiry) implements WorkloadJsonWebToken {\n        }\n        return new WorkloadJsonWebTokenImpl(token, sub, Set.copyOf(audience), expiry);\n    }\n\n    private static WorkloadCertificateDocument toWorkloadCertificate(X509SVIDResponse response)\n            throws SpiffeConnectionException {\n        List<X509SVID> svids = response.getSvidsList();\n        if (svids.isEmpty()) {\n            throw new SpiffeConnectionException(\"X.509-SVID response from SPIRE agent contains no SVIDs\");\n        }\n        X509SVID svid = svids.get(0);\n\n        String protoSpiffeId = svid.getSpiffeId();\n        SpiffeValidator.validateSpiffeId(protoSpiffeId);","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/spiffe-client/runtime/src/main/java/io/quarkus/spiffe/client/runtime/internal/SpiffeClientImpl.java#L255-L291","documentation":"The Quarkus SPIFFE client fetches JWT-SVIDs from a SPIRE Workload API agent. After parsing the token payload, it checks the 'exp' claim and rejects any token whose expiry is already in the past. This prevents callers from using a token that cannot possibly be valid for authentication.","triggerScenarios":"Calling fetchWorkloadJsonWebTokens (via WorkloadApiClient) when the SPIRE agent returns a JWT-SVID whose parsed 'exp' claim epoch-second value is before Instant.now().","commonSituations":"SPIRE agent clock skew with the application container; a SPIRE server issuing short-lived SVIDs (e.g. 5 min TTL) that expired between signing and delivery; NTP drift in Kubernetes pods; agent returning cached/stale tokens.","solutions":["Synchronize clocks between the application and the SPIRE agent/server (NTP, kubelet clock settings).","Increase the JWT-SVID TTL in the SPIRE server entry (ttl_seconds) or re-request tokens just before use instead of caching them.","Retry the fetch; a fresh token should have a future exp if clocks agree.","Check the SPIRE agent logs/health for stale or cached SVIDs and restart the agent if it serves expired tokens."],"exampleFix":"// before: token fetched once at startup and reused\nWorkloadJsonWebToken jwt = client.fetchWorkloadJsonWebTokens(...);\n// after: re-fetch when close to expiry\nWorkloadJsonWebToken jwt = expiry.isBefore(Instant.now().plusSeconds(60))\n        ? client.fetchWorkloadJsonWebTokens(...)\n        : cached;","handlingStrategy":"retry","validationCode":"byte[] token = fetchTokenRaw();\nlong exp = parseClaim(token, \"exp\");\nif (Instant.ofEpochSecond(exp).isBefore(Instant.now().plusSeconds(30))) {\n    // refresh before use\n}\n","typeGuard":null,"tryCatchPattern":"try {\n    jwt = client.fetchWorkloadJsonWebTokens(aud);\n} catch (SpiffeConnectionException e) {\n    if (e.getMessage().contains(\"already expired\")) {\n        jwt = retryWithBackoff(() -> client.fetchWorkloadJsonWebTokens(aud));\n    } else throw e;\n}","preventionTips":["Fetch SVIDs just-in-time rather than caching at startup","Keep NTP/clock sync enabled in all containers","Refresh when within a safety margin (e.g. 60s) of expiry","Set adequate JWT-SVID TTL in SPIRE registration entries"],"tags":["spiffe","jwt","spire","clock-skew"],"backgroundTag":"jwt-token-expired","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}