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
- Enter a newly generated code from the correct authenticator entry and retry
- Ensure NTP/time sync on the CAS server to keep TOTP windows aligned
- If several devices are registered, verify the accountId selection matches the device used
- 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
- NTP-sync all CAS nodes
- Share the GAuth token repository in clusters so consumption is consistent
- When multiple devices are registered, pass the matching accountId
- Prevent login-page resubmission that replays consumed codes
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to authenticate code
- Failed to authenticate code
- Failed to authenticate code
- cannot be found in the registry
- cannot reuse OTP
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)