{"record":{"id":"eb2a7c71278c9d08","repo":"apereo/cas","slug":"authorization-of-otp-token-has-failed-eb2a7c","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/GoogleAuthenticatorConfirmAccountRegistrationAction.java","lineNumber":58,"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        Objects.requireNonNull(account, \"Account cannot be null\");\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                accountRegistrationVerified(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 (!isAccountRegistrationVerified(requestContext, account)) {\n            LOGGER.warn(\"Account registration is not verified for [{}]\", account.getId());\n            throw new FailedLoginException(\"Unauthorized account registration attempt for id \" + account.getId());\n        }\n\n        accountRegistrationUnverified(requestContext, account);\n        return success();\n    }\n\n    protected void accountRegistrationVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {\n        account.getProperties().add(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);\n        repository.update(account);\n    }\n\n    protected void accountRegistrationUnverified(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/GoogleAuthenticatorConfirmAccountRegistrationAction.java#L40-L76","documentation":"During account registration confirmation, GoogleAuthenticatorConfirmAccountRegistrationAction validates the submitted OTP token with the newly registered account. If validation fails (null result) it logs this warning and throws FailedLoginException, aborting the registration-confirmation webflow step.","triggerScenarios":"doExecuteInternal runs with a GoogleAuthenticatorTokenCredential whose token validator.validate(authentication, tokenCredential) returns null — code wrong, expired (outside window), reused, or not for the registered account.","commonSituations":"User scans the QR code with the wrong app account so codes don't match; clock skew between server and phone; user waits too long before submitting the code; token already consumed by a prior attempt.","solutions":["Have the user retry with a freshly generated code immediately after scanning the QR code","Synchronize clocks (NTP) on the CAS server and the user's device; widen the validation window if needed","Ensure the account being confirmed matches the one whose QR code was scanned (registrationId/accountId)","Clear partially registered state and restart the registration flow if the stored secret is stale"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// client-side sanity check before submitting\nboolean codeLooksValid = code != null && code.matches(\"\\\\d{6}\");","typeGuard":null,"tryCatchPattern":"try {\n    event = confirmRegistrationAction.execute(requestContext);\n} catch (FailedLoginException e) {\n    // prompt user to retry with a freshly generated code\n}","preventionTips":["Submit codes immediately after generating them","Scan the QR with the correct app account","Synchronize device and server clocks","Restart registration to get a fresh secret if repeated failures occur"],"tags":["otp","mfa","webflow","registration"],"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"}