apereo/cas · error · FailedLoginException

Failed to authenticate code

Error message

Failed to authenticate code 

What it means

GoogleAuthenticatorDeleteAccountAction requires OTP re-verification before removing a GAuth account. When the submitted token fails validation (validator.validate returns null) it throws FailedLoginException 'Failed to authenticate code <token>', aborting the delete flow so an attacker cannot remove a device without proving possession.

Solutions

  1. Submit a fresh OTP generated by the authenticator entry bound to the account being deleted
  2. Verify the correct account is selected in the flow (multiple accounts need the right entry's code)
  3. Check server time sync if codes are consistently rejected
  4. Re-register the device if its secret no longer matches the stored account
Defensive patterns

Strategy: try-catch

Validate before calling

if (token == null || !/^\d{6}$/.test(token)) return error("Enter the current 6-digit code to confirm removal");

Try / catch

try {
    action.executeInternal(rc);
} catch (FailedLoginException e) {
    return error("Deletion not confirmed: enter a valid OTP for this account");
}

Prevention

When it happens

Trigger: doExecuteInternal receives a token for the delete-account flow, calls validator.validate(token, authentication); validatedToken is null (wrong/reused/expired code, secret mismatch) so the exception is thrown before accountRemovalVerified is called.

Common situations: User enters a code from the wrong authenticator entry while trying to remove the account; code already consumed; clock drift; mistyped code.

Understand the failure class

Related errors


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

Appendix: source

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

        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);

        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);
                accountRemovalVerified(requestContext, account);
                return success();
            }
            LOGGER.warn("Authorization of OTP token [{}] has failed", token);
            throw new FailedLoginException("Failed to authenticate code " + token);
        }

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

        LOGGER.debug("Deleting account [{}]", account.getId());
        repository.delete(account.getId());
        return success();
    }

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

    protected boolean isAccountRemovalVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {

View on GitHub (pinned to e7288fc434)