apereo/cas · error · FailedLoginException

Failed to authenticate code

Error message

Failed to authenticate code 

What it means

GoogleAuthenticatorValidateTokenAction validates the OTP during login and throws FailedLoginException 'Failed to authenticate code <token>' when validator.validate returns null — the token did not authorize against any registered account. Unlike the handler, when validation succeeds it stores the token only if configured (validate flag false means the token wasn't yet consumed upstream).

Solutions

  1. Enter a newly generated code from the correct authenticator entry and retry
  2. Ensure NTP/time sync on the CAS server to keep TOTP windows aligned
  3. If several devices are registered, verify the accountId selection matches the device used
  4. Check the mfa-gauth webflow properties: whether validate/store behavior marks tokens consumed consistently across nodes
Defensive patterns

Strategy: try-catch

Validate before calling

if (!/^\d{6}$/.test(token) && !/^\d{8}$/.test(token)) return error("Enter a valid 6- or 8-digit code");

Try / catch

try {
    validateTokenAction.executeInternal(rc);
} catch (FailedLoginException e) {
    return retryOtpPrompt("Invalid code — request a new one");
}

Prevention

When it happens

Trigger: doExecuteInternal calls validator.validate(tokenCredential, authentication) during the mfa-gauth webflow; a null result (invalid code, reused token, account not found, clock drift, accountId mismatch with multiple registered accounts) triggers the FailedLoginException.

Common situations: User enters an expired or already-used TOTP; secret mismatch after re-registration; server clock skew; user with multiple registered devices supplying a code without the matching accountId; login page resubmission replaying an old code.

Understand the failure class

Related errors


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

Appendix: source

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

    @Override
    protected @Nullable Event doExecuteInternal(final RequestContext requestContext) throws Throwable {
        val token = requestContext.getRequestParameters().getRequired(GoogleAuthenticatorSaveRegistrationAction.REQUEST_PARAMETER_TOKEN, String.class);
        val accountId = requestContext.getRequestParameters().getRequired(OneTimeTokenAccountConfirmSelectionRegistrationAction.REQUEST_PARAMETER_ACCOUNT_ID, Long.class);

        val authentication = WebUtils.getAuthentication(requestContext);
        val tokenCredential = new GoogleAuthenticatorTokenCredential(token, accountId);
        val validatedToken = validator.validate(authentication, tokenCredential);
        if (validatedToken != null) {
            val principal = authentication.getPrincipal().getId();
            LOGGER.debug("Validated OTP token [{}] successfully for [{}]", validatedToken, principal);
            val validate = requestContext.getRequestParameters().getBoolean(OneTimeTokenAccountSaveRegistrationAction.REQUEST_PARAMETER_VALIDATE);
            if (validate == null || !validate) {
                validator.store(validatedToken);
            }
            return success();
        }
        LOGGER.warn("Authorization of OTP token [{}] has failed for [{}]", token, authentication.getPrincipal().getId());
        throw new FailedLoginException("Failed to authenticate code " + token);
    }
}

View on GitHub (pinned to e7288fc434)