{"record":{"id":"e43e72a75e573586","repo":"apereo/cas","slug":"failed-to-authenticate-code-e43e72","errorCode":null,"errorMessage":"Failed to authenticate code ","messagePattern":"Failed to authenticate code ","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorDeleteAccountAction.java","lineNumber":58,"sourceCode":"        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\n    protected boolean isAccountRemovalVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {","sourceCodeStart":40,"sourceCodeEnd":76,"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#L40-L76","documentation":"GoogleAuthenticatorDeleteAccountAction requires OTP re-verification before removing a GAuth account. When the submitted token fails validation (validator.validate returns null) it throws FailedLoginException 'Failed to authenticate code <token>', aborting the delete flow so an attacker cannot remove a device without proving possession.","triggerScenarios":"doExecuteInternal receives a token for the delete-account flow, calls validator.validate(token, authentication); validatedToken is null (wrong/reused/expired code, secret mismatch) so the exception is thrown before accountRemovalVerified is called.","commonSituations":"User enters a code from the wrong authenticator entry while trying to remove the account; code already consumed; clock drift; mistyped code.","solutions":["Submit a fresh OTP generated by the authenticator entry bound to the account being deleted","Verify the correct account is selected in the flow (multiple accounts need the right entry's code)","Check server time sync if codes are consistently rejected","Re-register the device if its secret no longer matches the stored account"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (token == null || !/^\\d{6}$/.test(token)) return error(\"Enter the current 6-digit code to confirm removal\");","typeGuard":null,"tryCatchPattern":"try {\n    action.executeInternal(rc);\n} catch (FailedLoginException e) {\n    return error(\"Deletion not confirmed: enter a valid OTP for this account\");\n}","preventionTips":["Ensure the user uses the authenticator entry tied to the account being deleted","Keep server time synced","Request a fresh code after each failed attempt","Verify stored account secret still matches the device"],"tags":["gauth","otp","webflow","mfa"],"backgroundTag":"authentication-failed","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"}