apereo/cas · error · InvalidCibaRequestException

Backchannel token delivery mode cannot grant access tokens

Error message

Backchannel token delivery mode cannot grant access tokens

What it means

AccessTokenCibaGrantRequestExtractor.extractRequest throws InvalidCibaRequestException with this message when the registered service's backchannelTokenDeliveryMode parses to something other than POLL or PING. Only those modes can complete a CIBA grant at the token endpoint, so CAS refuses the access-token grant.

Solutions

  1. Set the service's backchannelTokenDeliveryMode to 'poll' or 'ping'
  2. If PUSH delivery is required, implement/use the push notification path instead of polling the token endpoint
  3. Note valueOf on an unknown mode string throws IllegalArgumentException before this check — ensure the value is a valid OidcBackchannelTokenDeliveryModes constant

Example fix

// before (service definition)
"backchannelTokenDeliveryMode": "push"
// after
"backchannelTokenDeliveryMode": "poll"
Defensive patterns

Strategy: validation

Validate before calling

var mode = service.getBackchannelTokenDeliveryMode();
if (!"poll".equalsIgnoreCase(mode) && !"ping".equalsIgnoreCase(mode)) {
    throw new IllegalStateException("CIBA token grant requires delivery mode poll|ping, got: " + mode);
}

Try / catch

try { return extractor.extractRequest(context); } catch (InvalidCibaRequestException e) { if (e.getMessage().contains("delivery mode")) { throw new InvalidGrantException("Unsupported CIBA delivery mode"); } throw e; }

Prevention

When it happens

Trigger: registeredService.getBackchannelTokenDeliveryMode() parses (case-insensitively) to a mode other than POLL or PING — e.g. PUSH or a typo — while servicing a CIBA token request.

Common situations: Admin configured delivery mode 'push'; typo like 'pol' would actually fail earlier at valueOf with IllegalArgumentException; client expects push but token endpoint only serves poll/ping grants.

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


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/fcd8feb92eee4ffe. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/ciba/AccessTokenCibaGrantRequestExtractor.java:72

        val decodedId = cibaFactory.decodeId(authRequestId);
        val cibaRequest = configurationContext.getTicketRegistry().getTicket(decodedId, OidcCibaRequest.class);

        val audit = AuditableContext.builder()
            .service(service)
            .registeredService(registeredService)
            .authentication(cibaRequest.getAuthentication())
            .build();
        val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);
        accessResult.throwExceptionIfNeeded();

        if (!registeredService.getSupportedGrantTypes().contains(getGrantType().getType())
            || StringUtils.isBlank(registeredService.getBackchannelClientNotificationEndpoint())
            || StringUtils.isBlank(registeredService.getBackchannelTokenDeliveryMode())) {
            throw new InvalidCibaRequestException("Registered OpenID Connect relying party does not support backchannel authentication requests");
        }
        val deliveryMode = OidcBackchannelTokenDeliveryModes.valueOf(registeredService.getBackchannelTokenDeliveryMode().toUpperCase(Locale.ENGLISH));
        if (deliveryMode != OidcBackchannelTokenDeliveryModes.POLL && deliveryMode != OidcBackchannelTokenDeliveryModes.PING) {
            throw new InvalidCibaRequestException("Backchannel token delivery mode cannot grant access tokens");
        }
        if (!cibaRequest.isReady()) {
            throw new InvalidCibaRequestException("CIBA request %s is not ready to grant access tokens".formatted(authRequestId));
        }
        
        return AccessTokenRequestContext.builder()
            .service(service)
            .authentication(cibaRequest.getAuthentication())
            .registeredService(registeredService)
            .responseType(getResponseType())
            .grantType(getGrantType())
            .scopes(cibaRequest.getScopes())
            .userProfile(profile)
            .clientId(registeredService.getClientId())
            .generateRefreshToken(registeredService.isGenerateRefreshToken())
            .build();

    }

View on GitHub (pinned to e7288fc434)