{"record":{"id":"aa24a4819e56b852","repo":"apereo/cas","slug":"authorization-of-otp-token-has-failed-aa24a4","errorCode":null,"errorMessage":"Authorization of OTP token [{}] has failed","messagePattern":"Authorization of OTP token \\[(.+?)\\] has failed","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":57,"sourceCode":"    protected @Nullable Event doExecuteInternal(final RequestContext requestContext) throws Throwable {\n        val requestParameters = requestContext.getRequestParameters();\n        val accountId = requestParameters.getRequired(OneTimeTokenAccountConfirmSelectionRegistrationAction.REQUEST_PARAMETER_ACCOUNT_ID, Long.class);\n        val validate = requestParameters.getBoolean(OneTimeTokenAccountSaveRegistrationAction.REQUEST_PARAMETER_VALIDATE);\n        val account = repository.get(accountId);\n\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","sourceCodeStart":39,"sourceCodeEnd":75,"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#L39-L75","documentation":"GoogleAuthenticatorDeleteAccountAction validates the OTP token supplied when removing a device. If validator.validate returns null the removal request is rejected: this warning is logged and FailedLoginException is thrown, so the account is not deleted.","triggerScenarios":"doExecuteInternal receives a GoogleAuthenticatorTokenCredential for account removal whose token fails validation (wrong code, expired window, reused token, or token not bound to the account being deleted).","commonSituations":"User confirms device removal with a stale or mistyped code; token already consumed; wrong registrationId selected so codes are validated against the wrong account secret.","solutions":["Ask the user to submit a fresh code from the authenticator entry corresponding to the device being deleted","Verify the accountId/registrationId selection matches the account whose token is submitted","Check clock sync and validation window settings if all valid codes are rejected","If the account secret in the repository is corrupted, re-register the device before removal"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"boolean codeLooksValid = code != null && code.matches(\"\\\\d{6}\");\n// and confirm the selected device id matches the code source","typeGuard":null,"tryCatchPattern":"try {\n    event = deleteAccountAction.execute(requestContext);\n} catch (FailedLoginException e) {\n    // prompt for a fresh code bound to the device being removed\n}","preventionTips":["Use a code from the specific app entry being deleted","Submit codes promptly after generation","Keep clocks synchronized","Re-register the device if its stored secret is suspect"],"tags":["otp","mfa","webflow","account-removal"],"backgroundTag":"authentication-required","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"}