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
- Have the user re-enter the exact user code displayed by the relying party
- Confirm the verification request uses the same requestId/auth request that originally carried the user code
- Check for trailing whitespace/case differences between submitted and stored codes
- 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
- Display the user code clearly and enforce exact re-entry on the device UI
- Trim/canonicalize user codes before submission
- Keep the verification session tied to the same requestId as the original CIBA request
- Expire and re-issue user codes rather than allowing unlimited retries
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
- Unable to verify JWT assertion with any of the configured…
- Registered OpenID Connect relying party does not support…
- Backchannel token delivery mode cannot grant access tokens
- CIBA request is not ready to grant access tokens
- Backchannel token delivery mode cannot grant access tokens
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)