apereo/cas · error · FailedLoginException

Unauthorized account registration attempt for id

Error message

Unauthorized account registration attempt for id 

What it means

GoogleAuthenticatorConfirmAccountRegistrationAction throws this when no OTP/token was submitted for the step and the account registration is not already marked verified in the flow scope (isAccountRegistrationVerified returns false). It enforces that a GAuth account can only proceed through registration if it has been proven via OTP verification.

Solutions

  1. Complete the flow in order and submit the OTP on the registration confirmation screen
  2. Inspect the webflow transition configuration so the confirm-registration state cannot be entered without a token (bind/validate the request parameter)
  3. Check flow-scope handling: the verified marker is set by accountRegistrationVerified in a prior successful step; ensure sessions/flow execution aren't restarting
  4. If this occurs repeatedly for one client, clear cookies/restart flow to reset stale webflow state

Example fix

// before: action called with no token and unverified state
// FailedLoginException: Unauthorized account registration attempt for id 42
// after: ensure the flow state binds and requires the token
<var name="token" class="java.lang.String"/>
<transition on="submit" to="confirmRegistration" bind="true" validate="true"/>
Defensive patterns

Strategy: validation

Validate before calling

// guard before invoking the action
if ((token == null || token.isBlank()) && !isAccountRegistrationVerified(requestContext, account))
    throw new IllegalStateException("A valid OTP must accompany the confirm-registration step");

Try / catch

try {
    return action.executeInternal(requestContext);
} catch (FailedLoginException e) {
    return restartRegistrationFlow();
}

Prevention

When it happens

Trigger: doExecuteInternal is invoked with a null/empty token (user skipped the OTP prompt or hit a flow transition that bypassed it) and flow scope has no verified flag for the account — thrown before accountRegistrationUnverified/success is reached.

Common situations: Deep-linking or navigating back/forward in the CAS webflow out of order; a client/customized flow drops the token request parameter; user skips the 'enter code' screen; session/flow-scope state lost between steps causing the verified marker to disappear.

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/c4c8c89993f56551. 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:64

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

View on GitHub (pinned to e7288fc434)