passbolt/passbolt_api · error · BadRequestException

Unable to authenticate to Duo.

Error message

Unable to authenticate to Duo. {error}

What it means

Thrown by DuoSetupCallbackGetController::getAndAssertMfaDuoCallbackData when the Duo callback DTO reports an error after executing MfaDuoCallbackForm — passbolt could not authenticate the OAuth-style callback from Duo during MFA setup. The Duo error from the callback response is appended to the message.

Solutions

  1. Read the appended {error} detail and fix the root cause it reports (e.g. invalid credentials or denied prompt)
  2. Verify Duo client id, client secret, and API hostname in passbolt MFA settings match the Duo application
  3. Check server clock synchronization (NTP) since Duo signed responses are time-sensitive
  4. Ensure the callback URL configured in Duo matches the passbolt endpoint exactly
Defensive patterns

Strategy: try-catch

Validate before calling

// Before reaching the callback, confirm Duo config matches the Duo application
const params = new URLSearchParams(window.location.search);
if (!params.get('code') || !params.get('state')) {
  // malformed callback: restart setup instead of forwarding
}

Try / catch

try {
  await mfaService.completeDuoSetup(callbackParams);
} catch (e) {
  if (e instanceof BadRequestException && /Unable to authenticate to Duo/.test(e.message)) {
    // inspect e.message detail; typically restart setup after fixing credentials
  }
}

Prevention

When it happens

Trigger: Duo redirects back to /mfa/verify Duo setup callback with an error parameter (user denied the prompt, invalid state nonce, wrong Duo client id/secret, or expired request) and the form execution yields a DTO error.

Common situations: Mismatched Duo application credentials (client secret / integration key) between Duo admin console and passbolt config; clock skew invalidating signed responses; user clicking 'Deny' in Duo prompt; reverse proxy stripping query parameters from the callback URL.

Understand the failure class

Related errors


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

Appendix: source

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

    /**
     * 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)