apereo/cas · error · IllegalArgumentException
JWT audience is invalid
Error message
JWT audience is invalid
What it means
readAudience normalizes the JWT 'aud' claim to a list of strings, accepting a single string or a list of all-strings. Anything else (non-string values, mixed list, null with wrong shape) throws IllegalArgumentException("JWT audience is invalid").
Solutions
- Set 'aud' in the key-binding JWT to either a single string or an array of strings
- Decode the JWT and inspect the aud claim's JSON type
- Fix the JWT-building library/config that emits the malformed audience
Example fix
// before
claims.setAudience(12345); // numeric audience
// after
claims.setAudience("https://cas.example.org/cas/oidc"); // string audience Defensive patterns
Strategy: validation
Validate before calling
Object aud = claims.get("aud");
boolean ok = aud instanceof String || (aud instanceof List<?> l && l.stream().allMatch(String.class::isInstance)); Prevention
- Always set aud as string or array of strings
- Verify token payload with a decoder before sending
- Pin the JWT library's claim-typing behavior
When it happens
Trigger: validateKeyBindingJwt reads 'aud' from the key-binding JWT and finds a claim that is neither a String nor a List whose elements are all Strings — e.g. aud as a number, object, or list containing non-strings.
Common situations: Client libraries encoding aud as an object or nested list; manually crafted key-binding JWTs with a numeric client_id; serialization frameworks turning single aud into an unexpected type.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- JWT string claim is missing or invalid
- Proof audience does not match credential issuer
- JWT time claim is invalid
- Resource ID already exists in namespace .
- Username is null.
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/6ab8ba3cdcb50733.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/vc/presentation/OidcVerifiableCredentialPresentationResponseEndpointController.java:464
val numericDate = new BigDecimal(value.toString());
val components = numericDate.divideAndRemainder(BigDecimal.ONE);
return Instant.ofEpochSecond(components[0].longValueExact(),
components[1].movePointRight(9).longValueExact());
} catch (final ArithmeticException exception) {
throw new IllegalArgumentException("JWT time claim is invalid", exception);
}
}
private static List<String> readAudience(final Map<String, Object> claims) {
val audience = claims.get("aud");
if (audience instanceof final String value) {
return List.of(value);
}
if (audience instanceof final List<?> values
&& values.stream().allMatch(String.class::isInstance)) {
return values.stream().map(String.class::cast).toList();
}
throw new IllegalArgumentException("JWT audience is invalid");
}
private static String requiredStringClaim(final Map<String, Object> claims, final String name) {
val value = claims.get(name);
if (!(value instanceof final String stringValue) || stringValue.isBlank()) {
throw new IllegalArgumentException("JWT string claim is missing or invalid");
}
return stringValue;
}
private static boolean constantTimeEquals(final String left, final String right) {
return MessageDigest.isEqual(left.getBytes(StandardCharsets.UTF_8), right.getBytes(StandardCharsets.UTF_8));
}
private static ResponseEntity<Map<String, Object>> buildResponse(final HttpStatus status,
final Map<String, Object> body) {
return ResponseEntity.status(status)
.cacheControl(CacheControl.noStore())View on GitHub (pinned to e7288fc434)