{"record":{"id":"e0b52387515aee69","repo":"passbolt/passbolt_api","slug":"a-duo-state-cookie-is-required","errorCode":null,"errorMessage":"A Duo state cookie is required.","messagePattern":"A Duo state cookie is required\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php","lineNumber":188,"sourceCode":"            throw new FormValidationException($msg, $mfaDuoCallbackForm);\n        }\n\n        return $mfaDuoCallbackDto;\n    }\n\n    /**\n     * Consume the duo state cookie containing the user authentication token id and assert the format this one.\n     *\n     * @return string The token id stored in the cookie\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie is not defined\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie value is not a string\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the cookie value is not a valid uuid\n     */\n    private function consumeAndAssertCookieToken(): string\n    {\n        $cookieToken = (new MfaDuoStateCookieService())->readDuoStateCookieValue($this->getRequest());\n        if (is_null($cookieToken)) {\n            throw new BadRequestException(__('A Duo state cookie is required.'));\n        }\n        $cookieToExpire = new Cookie(MfaDuoStateCookieService::MFA_COOKIE_DUO_STATE);\n        $this->setResponse($this->getResponse()->withExpiredCookie($cookieToExpire));\n\n        if (!is_string($cookieToken)) {\n            throw new BadRequestException(__('The Duo state cookie value should be a string.'));\n        } elseif (!Validation::uuid($cookieToken)) {\n            throw new BadRequestException(__('The Duo state cookie should be a valid UUID.'));\n        }\n\n        return $cookieToken;\n    }\n\n    /**\n     * Add to the response the MFA verified cookie.\n     *\n     * @param \\App\\Utility\\UserAccessControl $uac User access control\n     * @param \\App\\Authenticator\\SessionIdentificationServiceInterface $sessionIdentificationService session ID service","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoSetupCallbackGetController.php#L170-L206","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const stateCookie = document.cookie.split('; ').find(c => c.startsWith('passbolt_mfa_duo_state='));\nif (!stateCookie) { throw new Error('Duo state cookie missing; restart the Duo setup flow.'); }","typeGuard":"function hasDuoStateCookie(request): boolean {\n  return typeof request.cookies?.passbolt_mfa_duo_state === 'string';\n}","tryCatchPattern":"try {\n  const res = await completeDuoSetupCallback(url);\n} catch (e) {\n  if (e.status === 400 && /Duo state cookie is required/.test(e.message)) {\n    restartDuoSetupFlow(); // cookie lost: redo redirect to Duo\n  } else { throw e; }\n}","preventionTips":["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."],"tags":["duo","mfa","cookie","bad-request"],"backgroundTag":"missing-required-argument","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}