{"record":{"id":"eed23a55dc09da52","repo":"apereo/cas","slug":"otp-format-is-invalid","errorCode":null,"errorMessage":"OTP format is invalid","messagePattern":"OTP format is invalid","errorType":"exception","errorClass":"AccountNotFoundException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-yubikey-core/src/main/java/org/apereo/cas/adaptors/yubikey/YubiKeyAuthenticationHandler.java","lineNumber":81,"sourceCode":"    @Override\n    public boolean supports(final Class<? extends Credential> clazz) {\n        return YubiKeyCredential.class.isAssignableFrom(clazz);\n    }\n\n    @Override\n    public boolean supports(final Credential credential) {\n        return YubiKeyCredential.class.isAssignableFrom(credential.getClass());\n    }\n\n    @Override\n    protected AuthenticationHandlerExecutionResult doAuthentication(final Credential credential, final Service service) throws GeneralSecurityException {\n        val yubiKeyCredential = (YubiKeyCredential) credential;\n\n        val otp = yubiKeyCredential.getToken();\n\n        if (!YubicoClient.isValidOTPFormat(otp)) {\n            LOGGER.debug(\"Invalid OTP format [{}]\", otp);\n            throw new AccountNotFoundException(\"OTP format is invalid\");\n        }\n\n        val authentication = Objects.requireNonNull(WebUtils.getInProgressAuthentication(),\n            \"CAS has no reference to an authentication event to locate a principal\");\n        val principal = authentication.getPrincipal();\n        val uid = principal.getId();\n        val publicId = registry.getAccountValidator().getTokenPublicId(otp);\n        if (!this.registry.isYubiKeyRegisteredFor(uid, publicId)) {\n            LOGGER.debug(\"YubiKey public id [{}] is not registered for user [{}]\", publicId, uid);\n            throw new AccountNotFoundException(\"YubiKey id is not recognized in registry\");\n        }\n\n        try {\n            val response = this.client.verify(otp);\n            val status = response.getStatus();\n            if (status.compareTo(ResponseStatus.OK) == 0) {\n                LOGGER.debug(\"YubiKey response status [{}] at [{}]\", status, response.getTimestamp());\n                return createHandlerResult(yubiKeyCredential, this.principalFactory.createPrincipal(uid));","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-yubikey-core/src/main/java/org/apereo/cas/adaptors/yubikey/YubiKeyAuthenticationHandler.java#L63-L99","documentation":"YubiKeyAuthenticationHandler.doAuthentication() validates the OTP's shape with YubicoClient.isValidOTPFormat before contacting the YubiCloud validation service. A token that is not a syntactically valid YubiKey OTP (roughly a 44-character modhex string prefixed by the device public id) cannot be valid, so the handler throws AccountNotFoundException to signal a bad credential.","triggerScenarios":"A user submits an empty, truncated, or non-modhex token (or any arbitrary string) as the YubiKey OTP during multifactor authentication.","commonSituations":"Users typing a static password instead of tapping the key; keyboard layout or mobile-browser input mangling modhex characters; API clients passing malformed tokens; whitespace or truncated paste in the otp field.","solutions":["Tap the YubiKey to generate a fresh OTP rather than typing a value; confirm it is ~44 modhex characters with the registered device prefix.","Fix client-side input issues (keyboard layout, mobile browser, truncation) that corrupt the token before submission.","Ensure the YubiKey is registered to the user's account so format checks and account mapping line up.","Trim whitespace and pass the raw modhex string unchanged to the validation endpoint."],"exampleFix":"// before\nString otp = request.getParameter(\"otp\").trim().toUpperCase();\n// after\nString otp = request.getParameter(\"otp\").trim();\nif (!YubicoClient.isValidOTPFormat(otp)) {\n    throw new AccountNotFoundException(\"OTP format is invalid\");\n}","handlingStrategy":"validation","validationCode":"if (otp == null || !otp.matches(\"[cbdefghijklnrtuv]{44}\")) { reject(\"OTP format is invalid\"); } // modhex, ~44 chars","typeGuard":"boolean isValidYubiOtp(String otp) { return otp != null && otp.matches(\"[cbdefghijklnrtuv]{32,48}\"); }","tryCatchPattern":"try {\n    handler.authenticate(credential);\n} catch (AccountNotFoundException e) {\n    // invalid OTP shape: prompt the user to tap the YubiKey again\n}","preventionTips":["Prompt users to tap (not type) the YubiKey to emit the OTP.","Trim whitespace and avoid transforms (uppercase, URL mangling) on the token.","Check keyboard-layout/mobile-input issues corrupting modhex characters.","Validate format client-side before submitting to CAS."],"tags":["yubikey","mfa","otp","validation"],"backgroundTag":"invalid-otp-format","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"}