apereo/cas · warning
Individual claims requested by OpenID scopes are forced to…
Error message
Individual claims requested by OpenID scopes are forced to be included in the ID token. This is a violation of the OpenID Connect specification and a workaround via dedicated CAS configuration. Claims should be requested from the userinfo/profile endpoints in exchange for an access token.
What it means
Per the OpenID Connect spec, individual claims requested via scope values must come from the userinfo endpoint, not be embedded in the ID token. CAS can be configured to force scope-derived claims into the ID token as a non-compliant workaround; this warning logs each time that mode is active and claims are collected into the token.
Solutions
- Disable the force-include configuration property so claims are served from the userinfo endpoint.
- Update the client to fetch claims from the userinfo endpoint using the issued access token.
- If the client truly cannot call userinfo, keep the flag but document the accepted spec deviation.
- Scope the flag to the specific registered service rather than globally to limit non-compliance.
Example fix
// before (application.yml)
cas:
authn:
oidc:
id-token:
include-id-token-claims: true
// after
cas:
authn:
oidc:
id-token:
include-id-token-claims: false Defensive patterns
Strategy: validation
Validate before calling
// Detect the non-compliant configuration before relying on spec behavior:
boolean claimsForcedIntoIdToken(org.springframework.core.env.Environment env) {
return env.getProperty("cas.authn.oidc.id-token.include-id-token-claims", Boolean.class, false);
} Prevention
- Leave the force-include flag disabled unless a legacy client requires it.
- Prefer userinfo-endpoint claim retrieval in client integrations.
- Scope the flag per registered service rather than globally.
- Document the deviation in deployment documentation when enabled.
When it happens
Trigger: buildJwtClaims runs with includeClaimsInIdTokenForcefully(context) returning true (the force-include ID token claims configuration is enabled); collectIdTokenClaims then adds scope-derived principal claims and the warning fires.
Common situations: Administrator enabled the 'include claims in ID token' property to satisfy a legacy client that cannot call userinfo; migrating an old OAuth2 client that expects all attributes in the token; a copied service template/environment carries the workaround flag.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Unable to use 'none' as ID token signing algorithm
- Unable to use 'none' as ID token encryption algorithm
- Authentication request does not include the
- Unable to use 'none' for the user-info signing algorithm
- Unable to use 'none' as user-info encryption algorithm
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/d78cdd76bbd43fff.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/OidcIdTokenGeneratorService.java:175
val attributes = authentication.getAttributes();
claims.setStringClaim(OAuth20Constants.CLIENT_ID, context.getRegisteredService().getClientId());
val authTime = accessToken.isStateless() || accessToken.getTicketGrantingTicket() == null
? authentication.getAuthenticationDate().toEpochSecond()
: ((AuthenticationAwareTicket) accessToken.getTicketGrantingTicket()).getAuthentication().getAuthenticationDate().toEpochSecond();
claims.setClaim(OidcConstants.CLAIM_AUTH_TIME, authTime);
if (attributes.containsKey(OAuth20Constants.STATE)) {
setClaim(claims, OAuth20Constants.STATE, attributes.get(OAuth20Constants.STATE).getFirst());
}
if (attributes.containsKey(OAuth20Constants.NONCE)) {
setClaim(claims, OAuth20Constants.NONCE, attributes.get(OAuth20Constants.NONCE).getFirst());
}
generateAccessTokenHash(accessToken, oidcRegisteredService, claims);
if (context.getResponseType() == OAuth20ResponseTypes.ID_TOKEN || includeClaimsInIdTokenForcefully(context)) {
FunctionUtils.doIf(includeClaimsInIdTokenForcefully(context),
_ -> LOGGER.warn("Individual claims requested by OpenID scopes are forced to be included in the ID token. "
+ "This is a violation of the OpenID Connect specification and a workaround via dedicated CAS configuration. "
+ "Claims should be requested from the userinfo/profile endpoints in exchange for an access token."))
.accept(claims);
collectIdTokenClaims(principal, context.getRegisteredService(), claims);
} else {
LOGGER.debug("Per OpenID Connect specification, individual claims requested by OpenID scopes "
+ "such as profile, email, address, etc. are only put "
+ "into the OpenID Connect ID token when the response type is set to id_token.");
}
claims.setStringClaim(OidcConstants.TXN, UUID.randomUUID().toString());
if (context.getGrantType() == OAuth20GrantTypes.CIBA) {
generateCibaClaims(context, claims);
}
Optional.ofNullable(accessToken.getAuthentication().getSingleValuedAttribute(OAuth20Constants.CLAIM_ACT, Map.class))
.ifPresent(value -> claims.setClaim(OAuth20Constants.CLAIM_ACT, value));
return claims;View on GitHub (pinned to e7288fc434)