apereo/cas · warning
Recaptcha score received is less than the threshold score…
Error message
Recaptcha score received is less than the threshold score defined for CAS
What it means
BaseCaptchaValidator.parseCaptchaResponse logs this warning when the reCAPTCHA verification response contains a 'score' at or below the configured threshold (recaptchaProperties.getScore()). The validator returns false, so CAPTCHA validation fails even though the token may have been verified by Google.
Solutions
- Lower cas.googleRecaptcha.score (e.g. from 0.9 to 0.5) to accept typical human scores.
- Confirm the site key/secret pair matches the intended reCAPTCHA version (v2 vs v3).
- Inspect logged verification responses to see actual score distribution and adjust the threshold.
- If scores are consistently ~0.1, investigate for bot traffic or misconfigured site key.
Example fix
// before cas.googleRecaptcha.score=0.9 // after cas.googleRecaptcha.score=0.5
Defensive patterns
Strategy: validation
Validate before calling
// client: read the returned score logic; server: adjust threshold
const score = verifyResponse.score;
if (score < 0.5) { requireStepUp(); } Type guard
function hasScore(node: Record<string, unknown>): node is { score: number } {
return typeof node.score === 'number' && node.score >= 0 && node.score <= 1;
} Try / catch
boolean ok = captchaValidator.validate(token, userAgent);
if (!ok) {
LOGGER.warn("reCAPTCHA validation failed (low score or unsuccessful)");
return error("captcha-failed");
} Prevention
- Set cas.googleRecaptcha.score based on observed real-user score distribution, not assumptions.
- Use a v2/v3 site key and secret pair consistent with the configured validator.
- Log and monitor verification responses to tune the threshold over time.
- Provide a fallback flow (e.g. email verification) for users unfairly rejected by low scores.
When it happens
Trigger: validate() -> parseCaptchaResponse() reads the verify-URL response JSON and finds node.score <= cas.googleRecaptcha.score; typical with reCAPTCHA v3 score-based verification.
Common situations: Threshold set too high for real-user traffic (v3 scores commonly fall between 0.3-0.9); suspicious/automated traffic producing low scores; response actually from reCAPTCHA v2 (no useful score semantics) being checked against a v3 threshold.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Recaptcha response/token is missing from the request
- Resource ID already exists in namespace .
- Username is null.
- Password is null.
- Password cannot be blank
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/6998f233e9d6e471.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-captcha-core/src/main/java/org/apereo/cas/web/BaseCaptchaValidator.java:86
val exec = HttpExecutionRequest.builder()
.method(HttpMethod.POST)
.url(recaptchaProperties.getVerifyUrl())
.headers(headers)
.entity("secret=%s&response=%s".formatted(recaptchaProperties.getSecret(), recaptchaResponse))
.build();
return HttpUtils.execute(exec);
}
protected boolean parseCaptchaResponse(final HttpResponse response) throws Exception {
try (val content = ((HttpEntityContainer) response).getEntity().getContent()) {
val result = IOUtils.toString(content, StandardCharsets.UTF_8);
if (StringUtils.isBlank(result)) {
throw new IllegalArgumentException("Unable to parse empty entity response from " + recaptchaProperties.getVerifyUrl());
}
LOGGER.debug("Recaptcha verification response received: [{}]", result);
val node = MAPPER.reader().readTree(result);
if (node.has("score") && node.get("score").doubleValue() <= recaptchaProperties.getScore()) {
LOGGER.warn("Recaptcha score received is less than the threshold score defined for CAS");
return false;
}
if (node.has("success") && node.get("success").booleanValue()) {
LOGGER.trace("Recaptcha has successfully verified the request");
return true;
}
}
return false;
}
}
View on GitHub (pinned to e7288fc434)