{"record":{"id":"cee9a8b020d75d81","repo":"apereo/cas","slug":"duo-security-passcode-authentication-has-failed","errorCode":null,"errorMessage":"Duo Security passcode authentication has failed","messagePattern":"Duo Security passcode authentication has failed","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/authn/DuoSecurityAuthenticationHandler.java","lineNumber":111,"sourceCode":"            .stream()\n            .filter(resolver -> resolver.supports(principal))\n            .findFirst()\n            .map(resolver -> resolver.resolve(principal))\n            .orElseThrow(() -> new IllegalStateException(\"Unable to resolve principal for Duo Security multifactor authentication\"));\n    }\n\n    private AuthenticationHandlerExecutionResult authenticateDuoPasscodeCredential(\n        final DuoSecurityPasscodeCredential credential) throws Exception {\n        try {\n            val duoAuthenticationService = multifactorAuthenticationProvider.getObject().getDuoAuthenticationService();\n            if (duoAuthenticationService.authenticate(credential).isSuccess()) {\n                val principal = principalFactory.createPrincipal(credential.getId());\n                return createHandlerResult(credential, principal, new ArrayList<>());\n            }\n        } catch (final Throwable e) {\n            LoggingUtils.error(LOGGER, e);\n        }\n        throw new FailedLoginException(\"Duo Security passcode authentication has failed\");\n    }\n\n    private AuthenticationHandlerExecutionResult authenticateDuoUniversalPromptCredential(\n        final DuoSecurityUniversalPromptCredential credential) throws Exception {\n        try {\n            val duoAuthenticationService = multifactorAuthenticationProvider.getObject().getDuoAuthenticationService();\n            val result = duoAuthenticationService.authenticate(credential);\n            if (result.isSuccess()) {\n                val principal = principalFactory.createPrincipal(result.getUsername(), result.getAttributes());\n                LOGGER.debug(\"Duo Security Universal Prompt has successfully authenticated [{}]\", Objects.requireNonNull(principal).getId());\n                return createHandlerResult(credential, principal, new ArrayList<>());\n            }\n        } catch (final Throwable e) {\n            LoggingUtils.error(LOGGER, e);\n        }\n        throw new FailedLoginException(\"Duo Security universal prompt authentication has failed\");\n    }\n","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/authn/DuoSecurityAuthenticationHandler.java#L93-L129","documentation":"FailedLoginException thrown by authenticateDuoApiCredential()'s passcode branch (authenticateDuoPasscodeCredential) when validating a Duo passcode credential fails for any reason. Any Throwable raised while verifying the passcode with the Duo service is logged via LoggingUtils.error, swallowed, and converted into this generic FailedLoginException.","triggerScenarios":"Submitting DuoSecurityPasscodeCredential through doAuthentication when the passcode verification call fails: wrong/expired passcode, Duo service unreachable, Duo API returning FAIL, or any exception in the verification path.","commonSituations":"User typing an old or already-used bypass code; user enrolled in a different Duo integration than the one CAS is configured against; Duo clock/signature problems; network outage between CAS and Duo. Because the cause is swallowed, the real reason only appears in the logged stack trace.","solutions":["Inspect the CAS log for the stack trace logged by LoggingUtils.error just before this exception to find the underlying cause.","Have the user re-enter a current, unused passcode (bypass codes and OTPs are single-use and expire).","Verify cas.authn.mfa.duo[0].* keys/host point to the same Duo integration the user is enrolled in.","Test the Duo integration connectivity (the /check endpoint) to rule out network or clock-skew issues."],"exampleFix":"// before: reused passcode\nDuoSecurityPasscodeCredential(id, \"123456789\")  // already consumed\n// after: fresh passcode from Duo Mobile or a new bypass code\nDuoSecurityPasscodeCredential(id, freshlyGeneratedPasscode)","handlingStrategy":"try-catch","validationCode":"// Validate passcode format before submission (Duo passcodes are numeric)\nif (passcode == null || !passcode.matches(\"\\\\d{6,10}\")) {\n    throw new IllegalArgumentException(\"Passcode format invalid\");\n}","typeGuard":"boolean isPasscodeCredential(Credential c) {\n    return c instanceof DuoSecurityPasscodeCredential;\n}","tryCatchPattern":"try {\n    result = duoHandler.authenticate(passcodeCredential);\n} catch (FailedLoginException e) {\n    // check CAS logs for the swallowed root cause, then prompt user for a fresh passcode\n    promptUserForNewPasscode();\n}","preventionTips":["Always inspect the LoggingUtils.error stack trace above this exception — the real cause is swallowed.","Warn users that passcodes/bypass codes are single-use.","Confirm users are enrolled in the exact Duo integration configured in CAS.","Test Duo connectivity with /check before blaming user input."],"tags":["duo","mfa","passcode","failed-login"],"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"}