{"record":{"id":"cf4d66150317bbc6","repo":"passbolt/passbolt_api","slug":"unable-to-validate-the-duo-callback-data","errorCode":null,"errorMessage":"Unable to validate the Duo callback data.","messagePattern":"Unable to validate the Duo callback data\\.","errorType":"validation","errorClass":"FormValidationException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php","lineNumber":170,"sourceCode":"     * @throws \\Cake\\Http\\Exception\\BadRequestException If Duo was not able to authenticate the user and provided error details\n     * @return \\Passbolt\\MultiFactorAuthentication\\Model\\Dto\\MfaDuoCallbackDto\n     */\n    private function getAndAssertMfaDuoCallbackData(): MfaDuoCallbackDto\n    {\n        $mfaDuoCallbackData = $this->getRequest()->getQueryParams();\n        $mfaDuoCallbackForm = new DuoCallbackForm();\n        $isValid = $mfaDuoCallbackForm->execute($mfaDuoCallbackData);\n        $mfaDuoCallbackDto = new MfaDuoCallbackDto($mfaDuoCallbackForm->getData());\n\n        if ($mfaDuoCallbackDto->hasError()) {\n            $msg = __('Unable to authenticate to Duo.');\n            $msg .= \" {$mfaDuoCallbackDto->formatError()}\";\n            throw new BadRequestException($msg);\n        }\n\n        if (!$isValid) {\n            $msg = __('Unable to validate the Duo callback data.');\n            throw new FormValidationException($msg, $mfaDuoCallbackForm);\n        }\n\n        return $mfaDuoCallbackDto;\n    }\n\n    /**\n     * Consume the duo state cookie containing the user authentication token id and assert the format this one.\n     *\n     * @return string The token id stored in the cookie\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie is not defined\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie value is not a string\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie value is not a valid uuid\n     */\n    private function consumeAndAssertCookieToken(): string\n    {\n        $cookieToken = (new MfaDuoStateCookieService())->readDuoStateCookieValue($this->getRequest());\n        if (is_null($cookieToken)) {\n            throw new BadRequestException(__('A Duo state cookie is required.'));","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php#L152-L188","documentation":"Thrown by DuoSetupCallbackGetController::getAndAssertMfaDuoCallbackData when MfaDuoCallbackForm::execute returns false, meaning the Duo callback payload failed form validation (required fields missing or invalid). A FormValidationException carrying the form errors is raised.","triggerScenarios":"Duo (or a hand-crafted request) hitting the Duo setup callback endpoint without the expected query parameters — missing code/state nonce, truncated redirect URL, or an attacker-probed request to the callback.","commonSituations":"Reverse proxy or bot filtering stripping callback query parameters; user bookmarking/pasting a partial callback URL; Duo state expired so the client receives a malformed retry; SSR tests hitting the endpoint directly without parameters.","solutions":["Restart the Duo MFA setup flow to get a fresh, complete callback redirect","Inspect the exception's form errors to see which callback fields failed validation","Ensure the Duo application's redirect/callback URL matches the passbolt route and no proxy strips query strings","Do not call the callback endpoint directly; it must be reached via Duo's redirect"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const required = ['state', 'duo_code']; // per MfaDuoCallbackForm\nconst params = new URLSearchParams(window.location.search);\nif (required.some((k) => !params.get(k))) {\n  // incomplete callback; restart the setup flow\n}","typeGuard":"function hasDuoCallbackParams(search) {\n  const p = new URLSearchParams(search);\n  return ['state'].every((k) => typeof p.get(k) === 'string' && p.get(k).length > 0);\n}","tryCatchPattern":"try {\n  await mfaService.completeDuoSetup(callbackParams);\n} catch (e) {\n  if (e.formErrors) {\n    // show field errors and restart the Duo setup flow\n  }\n}","preventionTips":["Always reach the callback via Duo's redirect, never by manual URL entry","Verify proxies don't strip query strings from the callback route","Restart the setup flow whenever the state nonce is missing or stale"],"tags":["mfa","duo","callback","form-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}