apereo/cas · error · FailedLoginException

Failed to authenticate code

Error message

Failed to authenticate code 

What it means

GoogleAuthenticatorConfirmAccountRegistrationAction requires the user to submit a valid OTP during the confirm-registration webflow step to prove device possession. When validator.validate returns null (the token fails authorization) it throws FailedLoginException 'Failed to authenticate code <token>'. Registration of the GAuth account therefore cannot be confirmed.

Solutions

  1. Re-enter a freshly generated OTP from the newly enrolled authenticator entry
  2. Verify the QR scan produced the correct secret (re-register the account to get a new QR)
  3. Check CAS server time synchronization (NTP)
  4. Confirm the same account being verified is the one whose secret matches the code
Defensive patterns

Strategy: try-catch

Validate before calling

// client: only submit fresh 6-digit codes
if (!/^\d{6}$/.test(token)) throw new IllegalArgumentException("Enter the 6-digit code from the app");

Try / catch

try {
    action.executeInternal(rc);
} catch (FailedLoginException e) {
    return error("Registration could not be confirmed: enter a new code from your authenticator");
}

Prevention

When it happens

Trigger: doExecuteInternal receives a token on the confirm-account-registration flow, calls validator.validate(token, authentication) and validatedToken is null — wrong code, reused code, clock drift, or account/secret mismatch — triggering the FailedLoginException before accountRegistrationVerified is called.

Common situations: User mistypes the code shown during registration; code comes from a different app entry; server clock skew; user retries an OTP already consumed by a prior validation.

Understand the failure class

Related errors


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

Appendix: source

Thrown at support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorConfirmAccountRegistrationAction.java:59

        val requestParameters = requestContext.getRequestParameters();
        val accountId = requestParameters.getRequired(OneTimeTokenAccountConfirmSelectionRegistrationAction.REQUEST_PARAMETER_ACCOUNT_ID, Long.class);
        val validate = requestParameters.getBoolean(OneTimeTokenAccountSaveRegistrationAction.REQUEST_PARAMETER_VALIDATE);
        val account = repository.get(accountId);
        Objects.requireNonNull(account, "Account cannot be null");
        if (BooleanUtils.isTrue(validate)) {
            val token = requestParameters.getRequired(GoogleAuthenticatorSaveRegistrationAction.REQUEST_PARAMETER_TOKEN, String.class);
            val authentication = WebUtils.getAuthentication(requestContext);
            val principal = authentication.getPrincipal().getId();
            LOGGER.debug("Validating account [{}] with token [{}] for principal [{}]", accountId, token, principal);
            val tokenCredential = new GoogleAuthenticatorTokenCredential(token, accountId);
            val validatedToken = validator.validate(authentication, tokenCredential);
            if (validatedToken != null) {
                LOGGER.debug("Validated OTP token [{}] successfully for [{}]", validatedToken, principal);
                accountRegistrationVerified(requestContext, account);
                return success();
            }
            LOGGER.warn("Authorization of OTP token [{}] has failed", token);
            throw new FailedLoginException("Failed to authenticate code " + token);
        }

        if (!isAccountRegistrationVerified(requestContext, account)) {
            LOGGER.warn("Account registration is not verified for [{}]", account.getId());
            throw new FailedLoginException("Unauthorized account registration attempt for id " + account.getId());
        }

        accountRegistrationUnverified(requestContext, account);
        return success();
    }

    protected void accountRegistrationVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {
        account.getProperties().add(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);
        repository.update(account);
    }

    protected void accountRegistrationUnverified(final RequestContext requestContext, final OneTimeTokenAccount account) {
        account.getProperties().remove(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);

View on GitHub (pinned to e7288fc434)