apereo/cas · warning
Backchannel token delivery mode cannot grant access tokens
Error message
Backchannel token delivery mode cannot grant access tokens
What it means
In CIBA (Client Initiated Backchannel Authentication), only the POLL and PING delivery modes allow the client to obtain tokens from the backchannel token endpoint; PUSH delivers the token to the client's notification endpoint instead. This validator warns and returns false when the registered service's delivery mode is anything other than POLL or PING, refusing the access-token grant.
Solutions
- Set the service definition's backchannelTokenDeliveryMode to 'poll' (or 'ping') in the OIDC registered service.
- Make the client's CIBA backchannel_authentication_mode match the registered mode.
- Fix typos in the mode value; an unparsable string fails validation.
- If PUSH is genuinely required, implement the client's CIBA notification endpoint and receive tokens there instead of calling the token endpoint.
- Re-run the CIBA flow to confirm the token request now passes this validator.
Example fix
// before (service definition) "backchannelTokenDeliveryMode": "push" // after "backchannelTokenDeliveryMode": "poll"
Defensive patterns
Strategy: validation
Validate before calling
// Validate the delivery mode before running a CIBA flow:
boolean isTokenGrantable(String mode) {
String m = mode == null ? "" : mode.trim().toUpperCase(java.util.Locale.ENGLISH);
return m.equals("POLL") || m.equals("PING");
} Prevention
- Set backchannelTokenDeliveryMode to 'poll' or 'ping' unless PUSH is fully implemented client-side.
- Match the client's requested backchannel mode with the registered service mode.
- Add a config lint that rejects unknown delivery modes in service definitions.
- Implement the CIBA notification endpoint when using PUSH instead of polling the token endpoint.
When it happens
Trigger: validateInternal validates a CIBA grant ticket; OidcBackchannelTokenDeliveryModes.valueOf(service.getBackchannelTokenDeliveryMode().toUpperCase()) resolves to PUSH (or a non-POLL/PING value), so granting an access token via the token endpoint is rejected.
Common situations: Service definition set to delivery mode 'push' while the client still polls the token endpoint; typo in the mode string so it never equals POLL/PING; client migrated to CIBA without updating the mode; test definitions copied from a PUSH-mode example.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Registered OpenID Connect relying party does not support…
- Backchannel token delivery mode cannot grant access tokens
- Unable to use 'none' for the user-info signing algorithm
- Unable to use 'none' as user-info encryption algorithm
- Service with client id is configured to encrypt tokens, yet…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/d67ac57c2cfa8a9c.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/ciba/OidcAccessTokenCibaGrantRequestValidator.java:54
}
@Override
protected boolean validateInternal(final WebContext context, final String grantType,
final ProfileManager manager, final UserProfile userProfile) {
val configurationContext = getConfigurationContext().getObject();
val authRequestId = configurationContext.getRequestParameterResolver().resolveRequestParameter(context, OidcConstants.AUTH_REQ_ID).orElseThrow();
val cibaFactory = (OidcCibaRequestFactory) configurationContext.getTicketFactory().get(OidcCibaRequest.class);
val decodedId = cibaFactory.decodeId(authRequestId);
val ticket = configurationContext.getTicketRegistry().getTicket(decodedId, OidcCibaRequest.class);
val result = ticket != null && !ticket.isExpired() && ticket.isReady();
LOGGER.debug("CIBA authentication request is [{}]", BooleanUtils.toString(result, "valid", "invalid"));
if (result) {
val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
configurationContext.getServicesManager(), ticket.getClientId(), OidcRegisteredService.class);
val deliveryMode = OidcBackchannelTokenDeliveryModes.valueOf(registeredService.getBackchannelTokenDeliveryMode().toUpperCase(Locale.ENGLISH));
if (deliveryMode != OidcBackchannelTokenDeliveryModes.POLL && deliveryMode != OidcBackchannelTokenDeliveryModes.PING) {
LOGGER.warn("Backchannel token delivery mode cannot grant access tokens");
return false;
}
}
return result;
}
@Override
public boolean supports(final WebContext context) {
val authRequestId = getConfigurationContext().getObject().getRequestParameterResolver().resolveRequestParameter(context, OidcConstants.AUTH_REQ_ID);
return super.supports(context) && authRequestId.isPresent();
}
@Override
protected OAuth20GrantTypes getGrantType() {
return OAuth20GrantTypes.CIBA;
}
}View on GitHub (pinned to e7288fc434)