{"record":{"id":"8fb47b551108fa44","repo":"apereo/cas","slug":"account-removal-is-not-verified-for","errorCode":null,"errorMessage":"Account removal is not verified for [{}]","messagePattern":"Account removal is not verified for \\[(.+?)\\]","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorDeleteAccountAction.java","lineNumber":62,"sourceCode":"\n        if (BooleanUtils.isTrue(validate)) {\n            val token = requestParameters.getRequired(GoogleAuthenticatorSaveRegistrationAction.REQUEST_PARAMETER_TOKEN, String.class);\n            val authentication = WebUtils.getAuthentication(requestContext);\n            val principal = authentication.getPrincipal().getId();\n            LOGGER.debug(\"Validating account [{}] with token [{}] for principal [{}]\", accountId, token, principal);\n            val tokenCredential = new GoogleAuthenticatorTokenCredential(token, accountId);\n            val validatedToken = validator.validate(authentication, tokenCredential);\n            if (validatedToken != null) {\n                LOGGER.debug(\"Validated OTP token [{}] successfully for [{}]\", validatedToken, principal);\n                accountRemovalVerified(requestContext, account);\n                return success();\n            }\n            LOGGER.warn(\"Authorization of OTP token [{}] has failed\", token);\n            throw new FailedLoginException(\"Failed to authenticate code \" + token);\n        }\n\n        if (!isAccountRemovalVerified(requestContext, account)) {\n            LOGGER.warn(\"Account removal is not verified for [{}]\", account.getId());\n            throw new FailedLoginException(\"Unauthorized account removal attempt \" + account.getId());\n        }\n\n        LOGGER.debug(\"Deleting account [{}]\", account.getId());\n        repository.delete(account.getId());\n        return success();\n    }\n\n    protected void accountRemovalVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {\n        account.getProperties().add(ACCOUNT_PROPERTY_REMOVAL_VERIFIED);\n        repository.update(account);\n    }\n\n    protected boolean isAccountRemovalVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {\n        return account.getProperties().contains(ACCOUNT_PROPERTY_REMOVAL_VERIFIED);\n    }\n}\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorDeleteAccountAction.java#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restart the device-removal flow from the start so the OTP verification step runs before deletion","Start a new webflow execution instead of resuming an old/expired one","If customizing the flow, ensure accountRemovalVerified(...) is invoked in the verification state before the delete action executes","Confirm flow/session scope persistence so the verified flag survives between steps"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure the removal-verification step ran before delete\nboolean verified = requestContext.getFlowScope().contains(\"googleAuthenticatorAccountRemovalVerified\");","typeGuard":null,"tryCatchPattern":"try {\n    event = action.execute(requestContext);\n} catch (FailedLoginException e) {\n    // send the user back to the token-verification step of removal\n}","preventionTips":["Do not bookmark the delete confirmation screen","Execute verification state before delete state in custom flows","Start fresh executions instead of resuming old ones","Preserve flow scope across the removal steps"],"tags":["webflow","mfa","account-removal","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}