apereo/cas · error · FailedLoginException
Failed to authenticate code
Error message
Failed to authenticate code
What it means
GoogleAuthenticatorAuthenticationHandler could not validate the submitted OTP token. GoogleAuthenticatorOneTimeTokenCredentialValidator returned null (token invalid, already used, wrong secret, or account mismatch) so the handler logs a warning and throws FailedLoginException. It means the user-entered TOTP/scratch code did not authorize against any registered GAuth account.
Solutions
- Have the user generate a fresh code from the authenticator app and retry — reused codes are rejected by tokenRepository.exists(uid, otp)
- Verify time sync (NTP) on the CAS server; large clock drift invalidates TOTP codes
- Check the registered account record's secretKey matches the QR code the user scanned
- If multiple accounts exist for the user, supply the correct accountId on the credential
Defensive patterns
Strategy: try-catch
Validate before calling
// client-side sanity before submit
if (!/^\d{6}$/.test(otp) && !/^\d{8}$/.test(otp)) reject("OTP must be 6 or 8 digits"); Try / catch
try {
result = handler.authenticate(tokenCredential);
} catch (FailedLoginException e) {
return failure("Invalid or expired code — generate a new one and retry");
} Prevention
- Keep CAS server clocks NTP-synchronized
- Never accept the same OTP twice in your UI flow
- Ensure users scan the QR for the exact account they authenticate as
- Prompt users to wait for a new 30-second window after failures
When it happens
Trigger: doAuthentication calls validator.validate(tokenCredential, authentication); when the returned validatedToken is null (no authorized account matched, token reused, account not found, clock drift past window) the handler throws FailedLoginException("Failed to authenticate code " + credential).
Common situations: User types an expired or already-consumed code; phone/server clock drift exceeding the allowed window; wrong secret registered for the account; user entering a TOTP from a different account when multiple accounts exist without specifying accountId.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- cannot be found in the registry
- cannot reuse OTP
- Failed to authenticate code
- Failed to authenticate code
- Failed to authenticate code
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/30f4df22296c8033.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/GoogleAuthenticatorAuthenticationHandler.java:72
public boolean supports(final Credential credential) {
return GoogleAuthenticatorTokenCredential.class.isAssignableFrom(credential.getClass());
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(final Credential credential, final Service service) throws Throwable {
val tokenCredential = (GoogleAuthenticatorTokenCredential) credential;
val authentication = Objects.requireNonNull(WebUtils.getInProgressAuthentication());
Objects.requireNonNull(authentication, "No authentication is available to determine the principal");
val validatedToken = validator.validate(authentication, tokenCredential);
if (validatedToken != null) {
val principal = authentication.getPrincipal().getId();
LOGGER.debug("Validated OTP token [{}] successfully for [{}]", validatedToken, principal);
validator.store(validatedToken);
LOGGER.debug("Creating authentication result and building principal for [{}]", principal);
return createHandlerResult(tokenCredential, principalFactory.createPrincipal(principal));
}
LOGGER.warn("Authorization of OTP token [{}] has failed", credential);
throw new FailedLoginException("Failed to authenticate code " + credential);
}
}
View on GitHub (pinned to e7288fc434)