apereo/cas · warning · FailedLoginException

Account removal is not verified for

Error message

Account removal is not verified for [{}]

What it means

Before deleting a device, the delete action requires that a prior verified token marked the removal as confirmed (isAccountRemovalVerified). When false, the user is attempting removal without completing the verification step; this warning is logged and FailedLoginException is thrown.

Solutions

  1. Restart the device-removal flow from the start so the OTP verification step runs before deletion
  2. Start a new webflow execution instead of resuming an old/expired one
  3. If customizing the flow, ensure accountRemovalVerified(...) is invoked in the verification state before the delete action executes
  4. Confirm flow/session scope persistence so the verified flag survives between steps
Defensive patterns

Strategy: validation

Validate before calling

// ensure the removal-verification step ran before delete
boolean verified = requestContext.getFlowScope().contains("googleAuthenticatorAccountRemovalVerified");

Try / catch

try {
    event = action.execute(requestContext);
} catch (FailedLoginException e) {
    // send the user back to the token-verification step of removal
}

Prevention

When it happens

Trigger: doExecuteInternal guard reached when no successful token validation previously stored the removal-verified flag: direct/back-navigation into the delete step, resumed flow execution, or skipped verification state.

Common situations: User refreshes or back-navigates to the delete confirmation screen; expired or reused webflow execution; customized flow bypassing the verification state; lost flow scope between steps.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/8fb47b551108fa44. 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:62

        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) {
        return account.getProperties().contains(ACCOUNT_PROPERTY_REMOVAL_VERIFIED);
    }
}

View on GitHub (pinned to e7288fc434)