apereo/cas · warning · FailedLoginException

Account registration is not verified for

Error message

Account registration is not verified for [{}]

What it means

After token validation, the confirm-registration action additionally requires that the registration-verified flag was previously stored in the webflow request context. If isAccountRegistrationVerified returns false, the user is attempting to finish registration without having completed the verification step, and a FailedLoginException is thrown.

Solutions

  1. Restart the MFA account registration flow from the beginning so the token verification step runs before confirmation
  2. Do not reuse or manually resume old webflow executions; start a fresh login/registration attempt
  3. If customizing the flow, ensure the state that calls accountRegistrationVerified(...) executes before the confirm action
  4. Check that the webflow scope holding the verified flag is not cleared (session/flow scope configuration)
Defensive patterns

Strategy: validation

Validate before calling

// before invoking the confirm step, ensure verification happened
// (in custom flow code) check flow scope flag before transitioning to confirm
boolean verified = requestContext.getFlowScope().contains("googleAuthenticatorRegistrationVerified");

Try / catch

try {
    event = action.execute(requestContext);
} catch (FailedLoginException e) {
    // redirect user to restart the registration flow from the first step
}

Prevention

When it happens

Trigger: doExecuteInternal reaches the guard when no prior successful token verification stored the account (e.g. user jumps directly to the confirm step, replayed flow execution, or flow state was resumed out of order).

Common situations: User bookmarks/back-navigates into the confirmation screen; flow execution id reused or expired; session loss between verification and confirmation steps; customizing the webflow and skipping the verification state.

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/67a895636c28554d. 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:63

        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) {
        account.getProperties().remove(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);
        repository.update(account);
    }

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

View on GitHub (pinned to e7288fc434)