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
- Complete the flow in order and submit the OTP on the registration confirmation screen
- Inspect the webflow transition configuration so the confirm-registration state cannot be entered without a token (bind/validate the request parameter)
- Check flow-scope handling: the verified marker is set by accountRegistrationVerified in a prior successful step; ensure sessions/flow execution aren't restarting
- 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
- Keep the webflow states in the documented order; don't deep-link past the OTP step
- Bind and require the token request parameter in the flow definition
- Avoid custom flow edits that skip the verification state
- Reset the flow (fresh execution) when scope state is suspect
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to authenticate code
- Failed to authenticate code
- Unauthorized account removal attempt
- 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/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)