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

  1. Lower cas.googleRecaptcha.score (e.g. from 0.9 to 0.5) to accept typical human scores.
  2. Confirm the site key/secret pair matches the intended reCAPTCHA version (v2 vs v3).
  3. Inspect logged verification responses to see actual score distribution and adjust the threshold.
  4. 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

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


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)