apereo/cas · warning · FailedLoginException
Authorization of OTP token
Error message
Authorization of OTP token [{}] has failed What it means
During account registration confirmation, GoogleAuthenticatorConfirmAccountRegistrationAction validates the submitted OTP token with the newly registered account. If validation fails (null result) it logs this warning and throws FailedLoginException, aborting the registration-confirmation webflow step.
Solutions
- Have the user retry with a freshly generated code immediately after scanning the QR code
- Synchronize clocks (NTP) on the CAS server and the user's device; widen the validation window if needed
- Ensure the account being confirmed matches the one whose QR code was scanned (registrationId/accountId)
- Clear partially registered state and restart the registration flow if the stored secret is stale
Defensive patterns
Strategy: retry
Validate before calling
// client-side sanity check before submitting
boolean codeLooksValid = code != null && code.matches("\\d{6}"); Try / catch
try {
event = confirmRegistrationAction.execute(requestContext);
} catch (FailedLoginException e) {
// prompt user to retry with a freshly generated code
} Prevention
- Submit codes immediately after generating them
- Scan the QR with the correct app account
- Synchronize device and server clocks
- Restart registration to get a fresh secret if repeated failures occur
When it happens
Trigger: doExecuteInternal runs with a GoogleAuthenticatorTokenCredential whose token validator.validate(authentication, tokenCredential) returns null — code wrong, expired (outside window), reused, or not for the registered account.
Common situations: User scans the QR code with the wrong app account so codes don't match; clock skew between server and phone; user waits too long before submitting the code; token already consumed by a prior attempt.
Related errors
- Failed to authenticate code
- Unauthorized account registration attempt for id
- Failed to authenticate code
- Failed to authenticate code
- No registration records could be found for
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/eb2a7c71278c9d08.
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:58
protected @Nullable Event doExecuteInternal(final RequestContext requestContext) throws Throwable {
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) {View on GitHub (pinned to e7288fc434)