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

  1. Restart the MFA Duo setup flow from the beginning so the state cookie is freshly set before the callback.
  2. Check the browser is not blocking or stripping cookies (SameSite, privacy mode, cookie extensions) for the passbolt domain.
  3. Verify the passbolt base URL / App.fullBaseUrl matches the domain used in the Duo redirect so the cookie is sent back.
  4. If cookie expiry is the cause, complete the callback promptly instead of leaving the redirect idle.
  5. 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

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


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 service

View on GitHub (pinned to 31c1bbc10f)