passbolt/passbolt_api · error · FormValidationException

Unable to validate the Duo callback data.

Error message

Unable to validate the Duo callback data.

What it means

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.

Solutions

  1. Restart the Duo MFA setup flow to get a fresh, complete callback redirect
  2. Inspect the exception's form errors to see which callback fields failed validation
  3. Ensure the Duo application's redirect/callback URL matches the passbolt route and no proxy strips query strings
  4. Do not call the callback endpoint directly; it must be reached via Duo's redirect
Defensive patterns

Strategy: try-catch

Validate before calling

const required = ['state', 'duo_code']; // per MfaDuoCallbackForm
const params = new URLSearchParams(window.location.search);
if (required.some((k) => !params.get(k))) {
  // incomplete callback; restart the setup flow
}

Type guard

function hasDuoCallbackParams(search) {
  const p = new URLSearchParams(search);
  return ['state'].every((k) => typeof p.get(k) === 'string' && p.get(k).length > 0);
}

Try / catch

try {
  await mfaService.completeDuoSetup(callbackParams);
} catch (e) {
  if (e.formErrors) {
    // show field errors and restart the Duo setup flow
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/cf4d66150317bbc6. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php:170

     * @throws \Cake\Http\Exception\BadRequestException If Duo was not able to authenticate the user and provided error details
     * @return \Passbolt\MultiFactorAuthentication\Model\Dto\MfaDuoCallbackDto
     */
    private function getAndAssertMfaDuoCallbackData(): MfaDuoCallbackDto
    {
        $mfaDuoCallbackData = $this->getRequest()->getQueryParams();
        $mfaDuoCallbackForm = new DuoCallbackForm();
        $isValid = $mfaDuoCallbackForm->execute($mfaDuoCallbackData);
        $mfaDuoCallbackDto = new MfaDuoCallbackDto($mfaDuoCallbackForm->getData());

        if ($mfaDuoCallbackDto->hasError()) {
            $msg = __('Unable to authenticate to Duo.');
            $msg .= " {$mfaDuoCallbackDto->formatError()}";
            throw new BadRequestException($msg);
        }

        if (!$isValid) {
            $msg = __('Unable to validate the Duo callback data.');
            throw new FormValidationException($msg, $mfaDuoCallbackForm);
        }

        return $mfaDuoCallbackDto;
    }

    /**
     * Consume the duo state cookie containing the user authentication token id and assert the format this one.
     *
     * @return string The token id stored in the cookie
     * @throws \Cake\Http\Exception\BadRequestException if the cookie is not defined
     * @throws \Cake\Http\Exception\BadRequestException if the cookie value is not a string
     * @throws \Cake\Http\Exception\BadRequestException if the cookie value is not a valid uuid
     */
    private function consumeAndAssertCookieToken(): string
    {
        $cookieToken = (new MfaDuoStateCookieService())->readDuoStateCookieValue($this->getRequest());
        if (is_null($cookieToken)) {
            throw new BadRequestException(__('A Duo state cookie is required.'));

View on GitHub (pinned to 31c1bbc10f)