{"record":{"id":"6930730eec102b65","repo":"apereo/cas","slug":"missing-required-principal-attribute-for-claim-s","errorCode":null,"errorMessage":"Missing required principal attribute for claim %s","messagePattern":"Missing required principal attribute for claim (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/enc/BaseOidcVerifiableCredentialEncoder.java","lineNumber":40,"sourceCode":" */\n@RequiredArgsConstructor\npublic abstract class BaseOidcVerifiableCredentialEncoder implements OidcVerifiableCredentialEncoder {\n    protected static final int CLAIM_VALIDITY_IN_MINUTES = 5;\n\n    protected final OidcConfigurationContext configurationContext;\n\n    protected Map<String, Object> produceClaims(final Principal principal, final OidcVerifiableCredentialValidationContext context) {\n        val properties = configurationContext.getCasProperties().getAuthn().getOidc().getVc();\n        val configurationId = context.resolveConfigurationId();\n        val configuration = properties.getIssuer().getCredentialConfigurations().get(configurationId);\n        Objects.requireNonNull(configuration, () -> \"Unable to locate credential configuration \" + configurationId);\n        val claims = new LinkedHashMap<String, Object>();\n\n        configuration.getClaims().forEach((claimName, claimProps) -> {\n            val rawValue = principal.getAttributes().get(claimName);\n\n            if (rawValue == null && claimProps.isMandatory()) {\n                throw new IllegalArgumentException(\"Missing required principal attribute for claim %s\".formatted(claimName));\n            }\n            if (rawValue != null) {\n                val claimValue = rawValue.size() == 1 ? rawValue.getFirst() : rawValue;\n                claims.put(claimName, !(claimValue instanceof Number) && NumberUtils.isParsable(claimValue.toString())\n                    ? NumberUtils.createNumber(claimValue.toString())\n                    : claimValue);\n            }\n        });\n        return claims;\n    }\n\n    protected OidcVerifiableCredentialConfigurationProperties resolveConfiguration(final String configurationId) {\n        val properties = configurationContext.getCasProperties().getAuthn().getOidc().getVc();\n        val configuration = properties.getIssuer().getCredentialConfigurations().get(configurationId);\n        Objects.requireNonNull(configuration, () -> \"Unable to locate credential configuration \" + configurationId);\n        return configuration;\n    }\n","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/enc/BaseOidcVerifiableCredentialEncoder.java#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nclaims: { \"email\": { mandatory: true } }  // principal has no 'email' attribute\n// after\nclaims: { \"email\": { mandatory: false } }  // or populate/release the email attribute","handlingStrategy":"validation","validationCode":"config.getClaims().forEach((claim, props) -> {\n    if (props.isMandatory() && !principal.getAttributes().containsKey(claim)) {\n        throw new IllegalArgumentException(\"Missing mandatory attribute: \" + claim);\n    }\n});","typeGuard":null,"tryCatchPattern":"try {\n    encoder.produceClaims(principal, ...);\n} catch (IllegalArgumentException e) {\n    log.error(\"VC claim validation failed: {}\", e.getMessage());\n}","preventionTips":["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."],"tags":["oidc","verifiable-credentials","attributes","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}