apereo/cas · error · IllegalArgumentException
Missing required principal attribute for claim
Error message
Missing required principal attribute for claim %s
What it means
Thrown by BaseOidcVerifiableCredentialEncoder.produceClaims when a claim configured as mandatory in the verifiable-credential configuration has no corresponding attribute on the authenticated principal. The encoder iterates configuration.getClaims(), looks each claim up in principal.getAttributes(), and fails fast if a required attribute is absent rather than emitting a credential with missing required claims.
Solutions
- Ensure the principal actually carries an attribute whose key exactly matches the configured claim name (check attribute repository, release policy, and casing).
- Set the claim's mandatory flag to false in the VC configuration if the attribute is genuinely optional.
- Debug the resolved principal attributes at authentication time and add the missing attribute to the returned attribute set.
- Fix the claim name in configuration.getClaims() to match an existing attribute key.
Example fix
// before
claims: { "email": { mandatory: true } } // principal has no 'email' attribute
// after
claims: { "email": { mandatory: false } } // or populate/release the email attribute Defensive patterns
Strategy: validation
Validate before calling
config.getClaims().forEach((claim, props) -> {
if (props.isMandatory() && !principal.getAttributes().containsKey(claim)) {
throw new IllegalArgumentException("Missing mandatory attribute: " + claim);
}
}); Try / catch
try {
encoder.produceClaims(principal, ...);
} catch (IllegalArgumentException e) {
log.error("VC claim validation failed: {}", e.getMessage());
} Prevention
- Keep the mandatory-claim list synchronized with the attribute release policy.
- Verify attribute names/casing end-to-end from repository to principal.
- Test credential issuance for each configured claim in CI.
When it happens
Trigger: Issuing a verifiable credential whose configured claim is marked mandatory=true (claimProps.isMandatory()) while the authenticated principal lacks an attribute with exactly that name; attribute name casing mismatch between the claim config and the released attribute.
Common situations: Admins configure cas.authn.oidc.vc claim mappings referencing attributes not enabled in the attribute release policy or not returned by the underlying attribute repository (LDAP/DB missing the field); renamed attributes in the identity source; case-sensitivity differences between claim name and attribute key.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- invalid_request
- Redirect URI cannot contain a fragment
- Invalid sector identifier uri
- Invalid logo uri from an unknown host
- Invalid policy uri from an unknown host
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/6930730eec102b65.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/enc/BaseOidcVerifiableCredentialEncoder.java:40
*/
@RequiredArgsConstructor
public abstract class BaseOidcVerifiableCredentialEncoder implements OidcVerifiableCredentialEncoder {
protected static final int CLAIM_VALIDITY_IN_MINUTES = 5;
protected final OidcConfigurationContext configurationContext;
protected Map<String, Object> produceClaims(final Principal principal, final OidcVerifiableCredentialValidationContext context) {
val properties = configurationContext.getCasProperties().getAuthn().getOidc().getVc();
val configurationId = context.resolveConfigurationId();
val configuration = properties.getIssuer().getCredentialConfigurations().get(configurationId);
Objects.requireNonNull(configuration, () -> "Unable to locate credential configuration " + configurationId);
val claims = new LinkedHashMap<String, Object>();
configuration.getClaims().forEach((claimName, claimProps) -> {
val rawValue = principal.getAttributes().get(claimName);
if (rawValue == null && claimProps.isMandatory()) {
throw new IllegalArgumentException("Missing required principal attribute for claim %s".formatted(claimName));
}
if (rawValue != null) {
val claimValue = rawValue.size() == 1 ? rawValue.getFirst() : rawValue;
claims.put(claimName, !(claimValue instanceof Number) && NumberUtils.isParsable(claimValue.toString())
? NumberUtils.createNumber(claimValue.toString())
: claimValue);
}
});
return claims;
}
protected OidcVerifiableCredentialConfigurationProperties resolveConfiguration(final String configurationId) {
val properties = configurationContext.getCasProperties().getAuthn().getOidc().getVc();
val configuration = properties.getIssuer().getCredentialConfigurations().get(configurationId);
Objects.requireNonNull(configuration, () -> "Unable to locate credential configuration " + configurationId);
return configuration;
}
View on GitHub (pinned to e7288fc434)