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
- Read the appended formatted-error detail for the specific Duo failure reason and act on it.
- Restart the MFA verification flow to obtain a fresh Duo authentication code.
- Verify Duo provider settings (client id, client secret, API hostname) in the MFA organization settings.
- Check server clock synchronization (NTP) — skewed time invalidates Duo tokens.
- 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
- Keep passbolt and Duo servers NTP-synchronized.
- Complete the Duo redirect quickly so auth codes do not expire.
- Never replay a consumed Duo auth code (no callback refresh).
- Double-check Duo client id/secret/hostname in MFA settings.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unable to authenticate to Duo.
- The duo authentication origin endpoint does not match the…
- The duo authentication subscriber does not match the…
- The token should reference an active Duo callback…
- Unable to validate the Duo callback data.
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)