apereo/cas · error · AuthenticationException

Unable to verify provided user code

Error message

Unable to verify provided user code 

What it means

OidcCibaController.verifyBackchannelVerificationRequest throws AuthenticationException with this message when the user code supplied during the backchannel authentication verification step does not match the user code stored on the CIBA request (or is blank while a user code is required). This guards the authentication-device flow against binding the wrong user's consent.

Solutions

  1. Have the user re-enter the exact user code displayed by the relying party
  2. Confirm the verification request uses the same requestId/auth request that originally carried the user code
  3. Check for trailing whitespace/case differences between submitted and stored codes
  4. If user codes are one-time/expiring, re-initiate the backchannel authentication request

Example fix

// before: blank or mismatched code submitted
verify(requestId, userCode="")  // AuthenticationException
// after: validate before calling
if (StringUtils.isNotBlank(userCode)) { verify(requestId, userCode.trim()); } else { rePromptForUserCode(); }
Defensive patterns

Strategy: validation

Validate before calling

if (StringUtils.isBlank(userCode) || !storedUserCodes.contains(userCode)) {
    throw new BadRequestException("user_code does not match the pending CIBA request");
}

Try / catch

try { controller.verifyBackchannelVerificationRequest(request, requestId, clientId, userCode); } catch (AuthenticationException e) { if (e.getMessage().startsWith("Unable to verify provided user code")) { showUserCodeRetryPrompt(); return; } throw e; }

Prevention

When it happens

Trigger: The CIBA request's authentication attributes contain OidcConstants.USER_CODE, and the submitted userCode is blank or not among the stored non-blank user code values; invoked from the CIBA verification endpoint.

Common situations: End user mistypes the user code shown on the authentication device; user code expired/rotated between request creation and verification; verification UI submitted an empty field; user code from a different CIBA transaction.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/web/controllers/ciba/OidcCibaController.java:143

    @Operation(summary = "Verify backchannel verification request",
        parameters = {
            @Parameter(name = "clientId", in = ParameterIn.PATH, description = "Client ID"),
            @Parameter(name = "requestId", in = ParameterIn.PATH, description = "Request ID"),
            @Parameter(name = "userCode", in = ParameterIn.QUERY, required = false, description = "Request ID")
        })
    public ResponseEntity verifyBackchannelVerificationRequest(
        @RequestParam(value = "userCode", required = false)
        final String userCode,
        @PathVariable final String clientId,
        @PathVariable final String requestId) throws Throwable {
        try {
            val registeredService = findRegisteredService(clientId);
            val cibaRequest = fetchOidcCibaRequest(requestId);
            if (cibaRequest.getAuthentication().containsAttribute(OidcConstants.USER_CODE)) {
                val userCodeValues = cibaRequest.getAuthentication().getAttributes().get(OidcConstants.USER_CODE)
                    .stream().map(Object::toString).filter(StringUtils::isNotBlank).toList();
                if (StringUtils.isBlank(userCode) || !userCodeValues.contains(userCode)) {
                    throw new AuthenticationException("Unable to verify provided user code " + userCode);
                }
            }

            for (val handler : tokenDeliveryHandlers) {
                if (BeanSupplier.isNotProxy(handler) && handler.supports(registeredService)) {
                    handler.deliver(registeredService, cibaRequest);
                }
            }

            val model = new LinkedHashMap<String, Object>();
            model.put("registeredService", registeredService);
            model.put("cibaRequest", cibaRequest);
            return ResponseEntity.ok(model);
        } catch (final Exception e) {
            LoggingUtils.error(LOGGER, e);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
    }

View on GitHub (pinned to e7288fc434)