floci-io/floci · error · InvalidTokenException
InvalidIdentityToken
InvalidIdentityToken
Error message
The web identity token is not a well-formed JWT
What it means
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.
Source
Thrown at src/main/java/io/github/hectorvent/floci/core/common/WebIdentityTokenVerifier.java:78
public Optional<String> peekIssuer(String token) {
return parseClaims(token).map(claims -> claims.path("iss").asText(null))
.filter(iss -> iss != null && !iss.isBlank());
}
/**
* Fully verifies {@code token}: RS256 signature against {@code publicKey}, {@code iss} equal to
* {@code expectedIssuer}, {@code aud} containing {@code requiredAudience}, and {@code exp}/
* {@code nbf} within {@link #CLOCK_SKEW_SECONDS}.
*
* <p>Claim comparisons are exact and case-sensitive, matching AWS treatment of OIDC claims.
*/
public WebIdentityToken verify(String token, RSAPublicKey publicKey, String expectedIssuer,
String requiredAudience) throws InvalidTokenException {
// Limit -1 keeps trailing empty segments, so an unsigned "header.payload." token is seen as
// three parts and rejected by the algorithm check below rather than as malformed.
String[] parts = token == null ? new String[0] : token.split("\\.", -1);
if (parts.length != 3) {
throw new InvalidTokenException("The web identity token is not a well-formed JWT");
}
JsonNode header = decodeJson(parts[0])
.orElseThrow(() -> new InvalidTokenException("The web identity token header is not valid JSON"));
String alg = header.path("alg").asText("");
if (!"RS256".equals(alg)) {
throw new InvalidTokenException("Unsupported web identity token algorithm: "
+ (alg.isEmpty() ? "none" : alg));
}
if (!signatureValid(parts[0] + "." + parts[1], parts[2], publicKey)) {
throw new InvalidTokenException("The web identity token signature is invalid");
}
JsonNode claims = decodeJson(parts[1])
.orElseThrow(() -> new InvalidTokenException("The web identity token payload is not valid JSON"));
String issuer = claims.path("iss").asText(null);View on GitHub (pinned to 62ff490619)
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
Example fix
// before
String token = oidcClient.getAccessToken(); // opaque, not a JWT
sts.assumeRoleWithWebIdentity(...);
// after
String token = oidcClient.getIdToken(); // signed RS256 JWT
if (token.split("\\.", -1).length != 3) throw new IllegalArgumentException("not a JWT");
sts.assumeRoleWithWebIdentity(...); Defensive patterns
Strategy: validation
Validate before calling
boolean isWellFormedJwt(String token) {
if (token == null) return false;
String[] parts = token.split("\\.", -1);
if (parts.length != 3) return false;
return !parts[0].isEmpty() && !parts[1].isEmpty() && !parts[2].isEmpty();
} Try / catch
try {
sts.assumeRoleWithWebIdentity(req);
} catch (InvalidIdentityTokenException e) {
// malformed token: re-authenticate, do not retry the same token
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/fae46ba71fc2786d.
Report an issue: GitHub.