apereo/cas · warning

Service definition [ ] does not request a pairwise subject…

Error message

Service definition [{}] does not request a pairwise subject type

What it means

The same provider logs this warning when the OidcRegisteredService exists but its subjectType is blank or 'public'. Pairwise subject identifiers are only generated for services with subjectType 'pairwise'; otherwise the provider falls back to the principal's plain id.

Solutions

  1. Set "subjectType": "pairwise" on the OidcRegisteredService definition.
  2. Confirm the sector identifier setup (sectorIdentifierUri or redirect URI host) so persistent IDs are stable.
  3. If public subjects are intended, switch to a non-pairwise username provider to remove the warning.

Example fix

// before
"subjectType": "public"
// after
"subjectType": "pairwise"
Defensive patterns

Strategy: validation

Validate before calling

if (oidcSvc == null || oidcSvc.getSubjectType() == null
        || "public".equalsIgnoreCase(oidcSvc.getSubjectType())) {
    LOGGER.warn("Pairwise provider needs subjectType=pairwise on service {}", oidcSvc == null ? null : oidcSvc.getName());
}

Type guard

boolean requestsPairwise(OidcRegisteredService svc) {
    return svc != null && "pairwise".equalsIgnoreCase(svc.getSubjectType());
}

Prevention

When it happens

Trigger: A client requests pairwise subject behavior (or is wired with PairwiseOidcRegisteredServiceUsernameAttributeProvider) but the OidcRegisteredService.subjectType is unset or set to 'public'; resolveUsernameInternal is called during ID token/sub claim generation.

Common situations: Forgetting to set subjectType=pairwise on the service JSON while expecting pairwise sub values; services created via management app defaulting to public; a request with specific subject type while service is public.

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/ccb6602e1b4c962f. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-services/src/main/java/org/apereo/cas/services/PairwiseOidcRegisteredServiceUsernameAttributeProvider.java:59

@EqualsAndHashCode(callSuper = true)
@Setter
public class PairwiseOidcRegisteredServiceUsernameAttributeProvider extends BaseRegisteredServiceUsernameAttributeProvider {

    @Serial
    private static final long serialVersionUID = 469929103943101717L;

    private PersistentIdGenerator persistentIdGenerator = new OidcPairwisePersistentIdGenerator();

    @Override
    public String resolveUsernameInternal(final RegisteredServiceUsernameProviderContext context) {
        if (context.getRegisteredService() == null || !OidcRegisteredService.class.isAssignableFrom(context.getRegisteredService().getClass())) {
            LOGGER.warn("Service definition [{}] is undefined or it's not an OpenId Connect relying party", context.getRegisteredService());
            return context.getPrincipal().getId();
        }
        val oidcSvc = (OidcRegisteredService) context.getRegisteredService();
        if (StringUtils.isBlank(oidcSvc.getSubjectType())
            || Strings.CI.equals(OidcSubjectTypes.PUBLIC.getType(), oidcSvc.getSubjectType())) {
            LOGGER.warn("Service definition [{}] does not request a pairwise subject type", oidcSvc);
            return context.getPrincipal().getId();
        }
        val sectorIdentifier = getSectorIdentifier(oidcSvc);
        val id = this.persistentIdGenerator.generate(context.getPrincipal(), sectorIdentifier);
        LOGGER.debug("Resolved username [{}] for pairwise access", id);
        return id;
    }

    private static @Nullable String getSectorIdentifier(final OidcRegisteredService client) {
        if (StringUtils.isNotBlank(client.getSectorIdentifierUri())) {
            val uri = UriComponentsBuilder.fromUriString(client.getSectorIdentifierUri()).build();
            return uri.getHost();
        }
        val uri = UriComponentsBuilder.fromUriString(client.getServiceId()).build();
        return uri.getHost();
    }

}

View on GitHub (pinned to e7288fc434)