passbolt/passbolt_api · error · BadRequestException
A Duo state cookie is required.
Error message
A Duo state cookie is required.
What it means
Thrown by DuoSetupCallbackGetController::consumeAndAssertCookieToken when the Duo state cookie (MFA_COOKIE_DUO_STATE) is absent from the incoming callback request. Passbolt sets this cookie when redirecting the user to Duo and requires it back on callback to bind the OAuth state and prevent CSRF. Without it the Duo setup callback cannot be trusted, so a 400 BadRequest is raised.
Solutions
- Restart the MFA Duo setup flow from the beginning so the state cookie is freshly set before the callback.
- Check the browser is not blocking or stripping cookies (SameSite, privacy mode, cookie extensions) for the passbolt domain.
- Verify the passbolt base URL / App.fullBaseUrl matches the domain used in the Duo redirect so the cookie is sent back.
- If cookie expiry is the cause, complete the callback promptly instead of leaving the redirect idle.
- Confirm no reverse proxy rewrites the Cookie header on the callback request.
Defensive patterns
Strategy: validation
Validate before calling
const stateCookie = document.cookie.split('; ').find(c => c.startsWith('passbolt_mfa_duo_state='));
if (!stateCookie) { throw new Error('Duo state cookie missing; restart the Duo setup flow.'); } Type guard
function hasDuoStateCookie(request): boolean {
return typeof request.cookies?.passbolt_mfa_duo_state === 'string';
} Try / catch
try {
const res = await completeDuoSetupCallback(url);
} catch (e) {
if (e.status === 400 && /Duo state cookie is required/.test(e.message)) {
restartDuoSetupFlow(); // cookie lost: redo redirect to Duo
} else { throw e; }
} Prevention
- Never open or bookmark the Duo callback URL directly; always arrive via the Duo redirect.
- Keep cookies enabled for the passbolt domain, including SameSite-appropriate settings.
- Complete the callback promptly; do not let the session idle through the Duo redirect.
- Ensure fullBaseUrl and Duo redirect URIs share the same domain so cookies scope correctly.
When it happens
Trigger: GET /mfa/duo/setup/callback is hit without the 'passbolt_mfa_duo_state' cookie: browser or reverse proxy stripped the cookie, the user opened the Duo redirect URL directly, cookies were blocked (third-party cookie policy), or the flow was restarted in a new browser/incognito session.
Common situations: Corporate proxies or browser privacy extensions dropping cookies; SameSite policies blocking the cookie on the Duo redirect; user bookmarking the Duo callback URL; load balancer routing the callback to a different domain than the one that set the cookie.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- A Duo state cookie is required.
- Could not create MFA verified cookie.
- The authentication token should be a valid UUID.
- The Duo state cookie should be a valid UUID.
- The Duo state cookie should be a valid UUID.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/e0b52387515aee69.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php:188
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.'));
}
$cookieToExpire = new Cookie(MfaDuoStateCookieService::MFA_COOKIE_DUO_STATE);
$this->setResponse($this->getResponse()->withExpiredCookie($cookieToExpire));
if (!is_string($cookieToken)) {
throw new BadRequestException(__('The Duo state cookie value should be a string.'));
} elseif (!Validation::uuid($cookieToken)) {
throw new BadRequestException(__('The Duo state cookie should be a valid UUID.'));
}
return $cookieToken;
}
/**
* Add to the response the MFA verified cookie.
*
* @param \App\Utility\UserAccessControl $uac User access control
* @param \App\Authenticator\SessionIdentificationServiceInterface $sessionIdentificationService session ID serviceView on GitHub (pinned to 31c1bbc10f)