apereo/cas · error · FailedLoginException
Unauthorized account removal attempt
Error message
Unauthorized account removal attempt
What it means
GoogleAuthenticatorDeleteAccountAction throws this when no token was provided and the account removal has not been previously verified in flow scope (isAccountRemovalVerified is false). It blocks unverified account deletion — removal must be confirmed either by a prior OTP verification step or by submitting a valid token now.
Solutions
- Follow the flow: submit a valid OTP on the account-removal verification screen before deletion
- Fix flow definitions so the delete state cannot be reached without prior verification or a bound token
- Clear stale webflow state (new flow execution/session) if the verified flag was lost
- If integrating programmatically, always call the verification path (token submitted) before invoking delete
Defensive patterns
Strategy: validation
Validate before calling
// guard the action call
if (token == null && !isAccountRemovalVerified(requestContext, account))
throw new IllegalStateException("Account removal requires prior OTP verification"); Try / catch
try {
action.executeInternal(requestContext);
} catch (FailedLoginException e) {
return restartRemovalFlow();
} Prevention
- Do not deep-link to the delete-account state; always pass through verification
- Bind the token parameter in the webflow definition
- Preserve webflow session state between steps
- In custom integrations, always submit a token when calling this action
When it happens
Trigger: doExecuteInternal is invoked with null/empty token and flow scope lacks the removal-verified flag — e.g., the delete-account state is reached out of order or the verification step was skipped — throwing FailedLoginException 'Unauthorized account removal attempt <accountId>'.
Common situations: Navigating directly to the delete-account state via back/forward or deep link; customized webflow bypassing the verify state; lost webflow/session state dropping the verified marker; automation calling the action without a token.
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
- Unauthorized account registration attempt for id
- Failed to authenticate code
- Failed to authenticate code
- Google authenticator token credential is not assigned a…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/587a7428ffbde20e.
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:63
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)