passbolt/passbolt_api · error · BadRequestException
Unable to verify Duo authentication.
Error message
Unable to verify Duo authentication.
What it means
After consuming the callback token and checking the state, enable() delegates the user's Duo code verification to MfaDuoVerifyDuoCodeService, which exchanges the one-time Duo code with the Duo Universal SDK. Any Throwable in that verification (expired/already-used code, invalid code, network error to Duo API, client misconfiguration) is wrapped in this BadRequestException with the original error preserved as previous.
Solutions
- Restart the whole Duo flow — Duo codes are single-use and short-lived, so get a fresh prompt and code
- Check getPrevious() to distinguish an invalid/expired code from a Duo API connectivity error
- Verify the Duo org settings (client id, secret, api hostname) match the integration the user authenticated against
- Ensure the server can reach https://<api-hostname> (outbound HTTPS, DNS, proxy) e.g. curl the Duo endpoint
- Make the client submit the callback exactly once to avoid code replay
Example fix
// before
try {
$service->enable($uac, $dto, $token);
} catch (BadRequestException $e) { /* swallowed */ }
// after
try {
$service->enable($uac, $dto, $token);
} catch (BadRequestException $e) {
$this->log('Duo verify failed: ' . $e->getPrevious()?->getMessage());
return $this->redirect('/mfa/setup/duo'); // restart flow for a fresh code
} Defensive patterns
Strategy: try-catch
Try / catch
try {
$service->enable($uac, $dto, $token);
} catch (\Cake\Http\Exception\BadRequestException $e) {
$this->log('Duo code verification failed: ' . $e->getPrevious()?->getMessage());
// redirect to restart the Duo prompt for a fresh one-time code
} Prevention
- Submit the Duo callback exactly once — codes are single-use
- Check outbound connectivity to the Duo API hostname from the server
- Keep org settings' client id/secret/hostname consistent between prompt start and verification
- Treat any verification failure as flow-ending: restart with a new token and code
When it happens
Trigger: Calling enable() with a duoCode that Duo rejects: code already redeemed by a prior request, code expired (Duo codes are short-lived), wrong Duo client credentials/api hostname, or unreachable Duo API from the server.
Common situations: Browser double-submits the callback and the second request reuses the consumed code; server clock skew or slow flow exceeding Duo's code TTL; Duo integration hostname/client id mismatch between org settings and the client used to start the prompt; firewall blocks outbound HTTPS to Duo.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- A Duo state cookie is required.
- A Duo state cookie is required.
- Unable to verify Duo authentication.
- An authentication token state is required.
- Could not create MFA verified cookie.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/29ff992cc46a77e9.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoEnableService.php:97
MfaDuoCallbackDto $duoCallbackDto,
string $token
): AuthenticationToken {
if (!Validation::uuid($token)) {
throw new InvalidArgumentException('The authentication token should be a valid UUID.');
}
$authenticationTokenType = AuthenticationToken::TYPE_MFA_SETUP;
$authenticationToken = (new MfaDuoCallbackAuthenticationTokenService())
->consumeAndVerifyAuthenticationToken(
$uac,
$authenticationTokenType,
$token,
$duoCallbackDto->state
);
try {
(new MfaDuoVerifyDuoCodeService($authenticationTokenType, $this->duoClient))
->verify($uac, $duoCallbackDto->duoCode);
} catch (Throwable $th) {
throw new BadRequestException(__('Unable to verify Duo authentication.'), null, $th);
}
$this->enableProvider($uac);
return $authenticationToken;
}
/**
* Enable the provider for the operator.
*
* @param \App\Utility\UserAccessControl $uac The user access control
* @return void
* @throw InternalErrorException If it could not enable the Duo MFA provider.
*/
private function enableProvider(UserAccessControl $uac): void
{
try {
MfaAccountSettings::enableProvider($uac, MfaSettings::PROVIDER_DUO);
} catch (Throwable $th) {View on GitHub (pinned to 31c1bbc10f)