passbolt/passbolt_api · error · BadRequestException

Unable to authenticate to Duo.

Error message

Unable to authenticate to Duo. {error}

What it means

Thrown by DuoVerifyCallbackGetController::getAndAssertMfaDuoCallbackData when the Duo callback form executed but the resulting MfaDuoCallbackDto carries an error. The message is prefixed 'Unable to authenticate to Duo.' plus the formatted error from Duo's response (e.g. invalid code, access denied).

Solutions

  1. Read the appended formatted-error detail for the specific Duo failure reason and act on it.
  2. Restart the MFA verification flow to obtain a fresh Duo authentication code.
  3. Verify Duo provider settings (client id, client secret, API hostname) in the MFA organization settings.
  4. Check server clock synchronization (NTP) — skewed time invalidates Duo tokens.
  5. Ensure the callback is processed once; do not refresh/replay the callback URL after success.
Defensive patterns

Strategy: try-catch

Validate before calling

// before processing callback, ensure Duo returned no error param
const params = new URLSearchParams(callbackUrl.split('?')[1]);
if (params.has('error') || params.has('error_description')) { throw new Error('Duo returned an error: ' + params.get('error_description')); }

Try / catch

try {
  await duoVerifyCallback();
} catch (e) {
  if (e.status === 400 && /Unable to authenticate to Duo/.test(e.message)) {
    restartDuoVerifyFlow(); // fresh auth code from a new Duo prompt
  } else { throw e; }
}

Prevention

When it happens

Trigger: GET /mfa/duo/verify/callback where Duo redirected back with an error parameter, the Duo authorization code is invalid/expired/reused, or the state request key does not match, causing MfaDuoCallbackForm->execute() to report an error in the DTO.

Common situations: User denies the Duo prompt; Duo authentication code expired due to slow redirect; clock skew between passbolt and Duo servers; wrong Duo client id/secret/host in passbolt MFA settings; callback replayed twice (code reuse).

Understand the failure class

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoVerifyCallbackGetController.php:157

    /**
     * Get the Mfa Duo Callback data from the query and assert them.
     *
     * @throws \App\Error\Exception\FormValidationException If the data provided on the query does not validate
     * @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
     */

View on GitHub (pinned to 31c1bbc10f)