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

  1. Disable the force-include configuration property so claims are served from the userinfo endpoint.
  2. Update the client to fetch claims from the userinfo endpoint using the issued access token.
  3. If the client truly cannot call userinfo, keep the flag but document the accepted spec deviation.
  4. 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

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


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)