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
- Set "subjectType": "pairwise" on the OidcRegisteredService definition.
- Confirm the sector identifier setup (sectorIdentifierUri or redirect URI host) so persistent IDs are stable.
- 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
- Always set subjectType=pairwise on services using the pairwise username provider
- Configure a sectorIdentifierUri when multiple redirect URIs share one sector
- Document the expected subject type per client in the service registry
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
- Located claim [ ] mapped to attribute [ ], yet resolved…
- Service definition [ ] is undefined or it's not an OpenId…
- JWKS cannot contain expressions
- Unable to use 'none' as introspection signing algorithm
- Unable to use 'none' as introspection encryption algorithm
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)