{"record":{"id":"fae46ba71fc2786d","repo":"floci-io/floci","slug":"invalididentitytoken","errorCode":"InvalidIdentityToken","errorMessage":"The web identity token is not a well-formed JWT","messagePattern":"The web identity token is not a well-formed JWT","errorType":"validation","errorClass":"InvalidTokenException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/core/common/WebIdentityTokenVerifier.java","lineNumber":78,"sourceCode":"    public Optional<String> peekIssuer(String token) {\n        return parseClaims(token).map(claims -> claims.path(\"iss\").asText(null))\n                .filter(iss -> iss != null && !iss.isBlank());\n    }\n\n    /**\n     * Fully verifies {@code token}: RS256 signature against {@code publicKey}, {@code iss} equal to\n     * {@code expectedIssuer}, {@code aud} containing {@code requiredAudience}, and {@code exp}/\n     * {@code nbf} within {@link #CLOCK_SKEW_SECONDS}.\n     *\n     * <p>Claim comparisons are exact and case-sensitive, matching AWS treatment of OIDC claims.\n     */\n    public WebIdentityToken verify(String token, RSAPublicKey publicKey, String expectedIssuer,\n                                   String requiredAudience) throws InvalidTokenException {\n        // Limit -1 keeps trailing empty segments, so an unsigned \"header.payload.\" token is seen as\n        // three parts and rejected by the algorithm check below rather than as malformed.\n        String[] parts = token == null ? new String[0] : token.split(\"\\\\.\", -1);\n        if (parts.length != 3) {\n            throw new InvalidTokenException(\"The web identity token is not a well-formed JWT\");\n        }\n\n        JsonNode header = decodeJson(parts[0])\n                .orElseThrow(() -> new InvalidTokenException(\"The web identity token header is not valid JSON\"));\n        String alg = header.path(\"alg\").asText(\"\");\n        if (!\"RS256\".equals(alg)) {\n            throw new InvalidTokenException(\"Unsupported web identity token algorithm: \"\n                    + (alg.isEmpty() ? \"none\" : alg));\n        }\n\n        if (!signatureValid(parts[0] + \".\" + parts[1], parts[2], publicKey)) {\n            throw new InvalidTokenException(\"The web identity token signature is invalid\");\n        }\n\n        JsonNode claims = decodeJson(parts[1])\n                .orElseThrow(() -> new InvalidTokenException(\"The web identity token payload is not valid JSON\"));\n\n        String issuer = claims.path(\"iss\").asText(null);","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/core/common/WebIdentityTokenVerifier.java#L60-L96","documentation":"Thrown by WebIdentityTokenVerifier.verify when the web identity token supplied to STS AssumeRoleWithWebIdentity does not split into exactly three dot-separated JWT segments (header.payload.signature). split is called with limit -1 so trailing empty segments count: an unsigned 'header.payload.' token is treated as three parts and moves on to the algorithm check instead. The error surfaces as STS InvalidIdentityToken.","triggerScenarios":"Passing a null, empty, or truncated token; passing only two segments ('header.payload'); passing a token with four dots (double separator); or passing an opaque access token that is not a JWT at all in the WebIdentityToken parameter.","commonSituations":"Confusing the OIDC ID token with an opaque provider access token; copy-paste truncation of long JWTs; environment variables stripping characters; building the token by concatenation and omitting the signature segment.","solutions":["Check the token has exactly two '.' characters and three non-empty base64url segments before calling AssumeRoleWithWebIdentity","Make sure you pass the OIDC provider's signed JWT (usually the id_token), not an opaque access token","Re-fetch a fresh token from the identity provider and retry"],"exampleFix":"// before\nString token = oidcClient.getAccessToken(); // opaque, not a JWT\nsts.assumeRoleWithWebIdentity(...);\n\n// after\nString token = oidcClient.getIdToken(); // signed RS256 JWT\nif (token.split(\"\\\\.\", -1).length != 3) throw new IllegalArgumentException(\"not a JWT\");\nsts.assumeRoleWithWebIdentity(...);","handlingStrategy":"validation","validationCode":"boolean isWellFormedJwt(String token) {\n    if (token == null) return false;\n    String[] parts = token.split(\"\\\\.\", -1);\n    if (parts.length != 3) return false;\n    return !parts[0].isEmpty() && !parts[1].isEmpty() && !parts[2].isEmpty();\n}","typeGuard":null,"tryCatchPattern":"try {\n    sts.assumeRoleWithWebIdentity(req);\n} catch (InvalidIdentityTokenException e) {\n    // malformed token: re-authenticate, do not retry the same token\n}","preventionTips":["Pass the OIDC id_token, never an opaque access token","Validate three non-empty dot-separated segments before calling STS","Avoid manual string assembly of JWTs"],"tags":["sts","jwt","oidc","assume-role-with-web-identity","invalid-identity-token"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}