{"record":{"id":"c4c8c89993f56551","repo":"apereo/cas","slug":"unauthorized-account-registration-attempt-for-id","errorCode":null,"errorMessage":"Unauthorized account registration attempt for id ","messagePattern":"Unauthorized account registration attempt for id ","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorConfirmAccountRegistrationAction.java","lineNumber":64,"sourceCode":"        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) {\n        account.getProperties().remove(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);\n        repository.update(account);\n    }\n\n    protected boolean isAccountRegistrationVerified(final RequestContext requestContext, final OneTimeTokenAccount account) {\n        return account.getProperties().contains(ACCOUNT_PROPERTY_REGISTRATION_VERIFIED);","sourceCodeStart":46,"sourceCodeEnd":82,"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#L46-L82","documentation":"GoogleAuthenticatorConfirmAccountRegistrationAction throws this when no OTP/token was submitted for the step and the account registration is not already marked verified in the flow scope (isAccountRegistrationVerified returns false). It enforces that a GAuth account can only proceed through registration if it has been proven via OTP verification.","triggerScenarios":"doExecuteInternal is invoked with a null/empty token (user skipped the OTP prompt or hit a flow transition that bypassed it) and flow scope has no verified flag for the account — thrown before accountRegistrationUnverified/success is reached.","commonSituations":"Deep-linking or navigating back/forward in the CAS webflow out of order; a client/customized flow drops the token request parameter; user skips the 'enter code' screen; session/flow-scope state lost between steps causing the verified marker to disappear.","solutions":["Complete the flow in order and submit the OTP on the registration confirmation screen","Inspect the webflow transition configuration so the confirm-registration state cannot be entered without a token (bind/validate the request parameter)","Check flow-scope handling: the verified marker is set by accountRegistrationVerified in a prior successful step; ensure sessions/flow execution aren't restarting","If this occurs repeatedly for one client, clear cookies/restart flow to reset stale webflow state"],"exampleFix":"// before: action called with no token and unverified state\n// FailedLoginException: Unauthorized account registration attempt for id 42\n// after: ensure the flow state binds and requires the token\n<var name=\"token\" class=\"java.lang.String\"/>\n<transition on=\"submit\" to=\"confirmRegistration\" bind=\"true\" validate=\"true\"/>","handlingStrategy":"validation","validationCode":"// guard before invoking the action\nif ((token == null || token.isBlank()) && !isAccountRegistrationVerified(requestContext, account))\n    throw new IllegalStateException(\"A valid OTP must accompany the confirm-registration step\");","typeGuard":null,"tryCatchPattern":"try {\n    return action.executeInternal(requestContext);\n} catch (FailedLoginException e) {\n    return restartRegistrationFlow();\n}","preventionTips":["Keep the webflow states in the documented order; don't deep-link past the OTP step","Bind and require the token request parameter in the flow definition","Avoid custom flow edits that skip the verification state","Reset the flow (fresh execution) when scope state is suspect"],"tags":["gauth","webflow","mfa","registration"],"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"}