{"record":{"id":"7316de92d7912138","repo":"apereo/cas","slug":"authorization-of-otp-token-has-failed-for","errorCode":null,"errorMessage":"Authorization of OTP token [{}] has failed for [{}]","messagePattern":"Authorization of OTP token \\[(.+?)\\] has failed for \\[(.+?)\\]","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorValidateTokenAction.java","lineNumber":52,"sourceCode":"\n    @Override\n    protected @Nullable Event doExecuteInternal(final RequestContext requestContext) throws Throwable {\n        val token = requestContext.getRequestParameters().getRequired(GoogleAuthenticatorSaveRegistrationAction.REQUEST_PARAMETER_TOKEN, String.class);\n        val accountId = requestContext.getRequestParameters().getRequired(OneTimeTokenAccountConfirmSelectionRegistrationAction.REQUEST_PARAMETER_ACCOUNT_ID, Long.class);\n\n        val authentication = WebUtils.getAuthentication(requestContext);\n        val tokenCredential = new GoogleAuthenticatorTokenCredential(token, accountId);\n        val validatedToken = validator.validate(authentication, tokenCredential);\n        if (validatedToken != null) {\n            val principal = authentication.getPrincipal().getId();\n            LOGGER.debug(\"Validated OTP token [{}] successfully for [{}]\", validatedToken, principal);\n            val validate = requestContext.getRequestParameters().getBoolean(OneTimeTokenAccountSaveRegistrationAction.REQUEST_PARAMETER_VALIDATE);\n            if (validate == null || !validate) {\n                validator.store(validatedToken);\n            }\n            return success();\n        }\n        LOGGER.warn(\"Authorization of OTP token [{}] has failed for [{}]\", token, authentication.getPrincipal().getId());\n        throw new FailedLoginException(\"Failed to authenticate code \" + token);\n    }\n}\n","sourceCodeStart":34,"sourceCodeEnd":56,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/web/flow/GoogleAuthenticatorValidateTokenAction.java#L34-L56","documentation":"In GoogleAuthenticatorValidateTokenAction the OTP token passed validation but fails the authorization check that ties the validated token to the authenticated principal's registered GAuth account. CAS treats this as a failed login: a FailedLoginException is thrown with the message \"Failed to authenticate code <token>\". This indicates the token was syntactically valid but did not correspond to an account owned by the given principal.","triggerScenarios":"A one-time token is presented whose computed value matches but whose owning account is not linked to the current authentication's principal (wrong account binding, principal mismatch, or account registry lookup returning a registration for another user).","commonSituations":"Users switching accounts mid-session; tokens generated against a different registration; missing or corrupted GAuth account records in the registry after import/restore; custom token validators that skip account-ownership checks.","solutions":["Confirm the OTP was generated from the device registered to the authenticated principal","Check the GAuth account registry (JSON/JDBC/Mongo) for the principal's account and verify its secret and user id fields are correct","Re-register the device for the user if the stored secret does not match the authenticator app","Review any custom OneTimeTokenValidator/account-lookup beans for principal-mapping errors"],"exampleFix":"// before (account bound to wrong user)\n{\"id\": 1, \"username\": \"otheruser\", \"secret\": \"...\"}\n// after\n{\"id\": 1, \"username\": \"correctuser\", \"secret\": \"...\"}","handlingStrategy":"try-catch","validationCode":"if (validatedToken == null || !principal.getId().equals(validatedToken.getPrincipalId())) {\n    throw new FailedLoginException(\"OTP token not owned by current principal\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    authCtx = gauthValidateAction.execute(ctx);\n} catch (FailedLoginException e) {\n    LOGGER.warn(\"GAuth authorization failed: {}\", e.getMessage());\n    return flowError(e);\n}","preventionTips":["Re-register devices whose stored secrets drift from the authenticator app","Verify GAuth account registry records bind the correct username to each registration","Avoid sharing/replaying OTPs across sessions or accounts"],"tags":["gauth","otp","failed-login","principal-mismatch"],"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"}