{"record":{"id":"d67ac57c2cfa8a9c","repo":"apereo/cas","slug":"backchannel-token-delivery-mode-cannot-grant-acces-d67ac5","errorCode":null,"errorMessage":"Backchannel token delivery mode cannot grant access tokens","messagePattern":"Backchannel token delivery mode cannot grant access tokens","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/ciba/OidcAccessTokenCibaGrantRequestValidator.java","lineNumber":54,"sourceCode":"    }\n\n    @Override\n    protected boolean validateInternal(final WebContext context, final String grantType,\n                                       final ProfileManager manager, final UserProfile userProfile) {\n        val configurationContext = getConfigurationContext().getObject();\n        val authRequestId = configurationContext.getRequestParameterResolver().resolveRequestParameter(context, OidcConstants.AUTH_REQ_ID).orElseThrow();\n        val cibaFactory = (OidcCibaRequestFactory) configurationContext.getTicketFactory().get(OidcCibaRequest.class);\n        val decodedId = cibaFactory.decodeId(authRequestId);\n        val ticket = configurationContext.getTicketRegistry().getTicket(decodedId, OidcCibaRequest.class);\n        val result = ticket != null && !ticket.isExpired() && ticket.isReady();\n        LOGGER.debug(\"CIBA authentication request is [{}]\", BooleanUtils.toString(result, \"valid\", \"invalid\"));\n\n        if (result) {\n            val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(\n                configurationContext.getServicesManager(), ticket.getClientId(), OidcRegisteredService.class);\n            val deliveryMode = OidcBackchannelTokenDeliveryModes.valueOf(registeredService.getBackchannelTokenDeliveryMode().toUpperCase(Locale.ENGLISH));\n            if (deliveryMode != OidcBackchannelTokenDeliveryModes.POLL && deliveryMode != OidcBackchannelTokenDeliveryModes.PING) {\n                LOGGER.warn(\"Backchannel token delivery mode cannot grant access tokens\");\n                return false;\n            }\n        }\n\n        return result;\n    }\n\n    @Override\n    public boolean supports(final WebContext context) {\n        val authRequestId = getConfigurationContext().getObject().getRequestParameterResolver().resolveRequestParameter(context, OidcConstants.AUTH_REQ_ID);\n        return super.supports(context) && authRequestId.isPresent();\n    }\n\n    @Override\n    protected OAuth20GrantTypes getGrantType() {\n        return OAuth20GrantTypes.CIBA;\n    }\n}","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/ciba/OidcAccessTokenCibaGrantRequestValidator.java#L36-L72","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (service definition)\n\"backchannelTokenDeliveryMode\": \"push\"\n// after\n\"backchannelTokenDeliveryMode\": \"poll\"","handlingStrategy":"validation","validationCode":"// Validate the delivery mode before running a CIBA flow:\nboolean isTokenGrantable(String mode) {\n    String m = mode == null ? \"\" : mode.trim().toUpperCase(java.util.Locale.ENGLISH);\n    return m.equals(\"POLL\") || m.equals(\"PING\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["oidc","ciba","token","configuration"],"backgroundTag":"invalid-enum-value","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}