{"record":{"id":"0b6d4de3c6b84213","repo":"passbolt/passbolt_api","slug":"a-duo-state-cookie-is-required-0b6d4d","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/DuoVerifyCallbackGetController.php","lineNumber":180,"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":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/MultiFactorAuthentication/src/Controller/Duo/DuoVerifyCallbackGetController.php#L162-L198","documentation":"Identical guard to error 290 but in DuoVerifyCallbackGetController::consumeAndAssertCookieToken: the Duo state cookie is missing on the MFA verify callback. The cookie set before redirecting to Duo must be returned to prove the callback belongs to the same browser session.","triggerScenarios":"GET /mfa/duo/verify/callback without the passbolt_mfa_duo_state cookie: cookies blocked or stripped, callback opened directly/bookmarked, new browser session, or the cookie already consumed by an earlier callback attempt.","commonSituations":"Third-party cookie blocking during the Duo redirect; SameSite attribute conflicts; privacy extensions deleting cookies; user refreshing the callback after the cookie was expired/consumed on first hit.","solutions":["Restart the MFA Duo verification flow so the state cookie is set again before the callback.","Enable cookies for the passbolt domain (check SameSite, privacy mode, and blocking extensions).","Do not refresh or re-open the callback URL after it was processed once — the cookie is expired on consumption.","Verify App.fullBaseUrl and the Duo redirect host match so the cookie scope covers the callback."],"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 verify flow.'); }","typeGuard":"function hasDuoStateCookie(request): boolean {\n  return typeof request.cookies?.passbolt_mfa_duo_state === 'string';\n}","tryCatchPattern":"try {\n  await duoVerifyCallback();\n} catch (e) {\n  if (e.status === 400 && /Duo state cookie is required/.test(e.message)) {\n    restartDuoVerifyFlow(); // cookie missing/expired: redo redirect to Duo\n  } else { throw e; }\n}","preventionTips":["Always reach the verify callback via the Duo redirect, never a saved URL.","Enable cookies for the passbolt domain; verify SameSite behavior with third-party redirects.","Do not refresh the callback — the state cookie is expired on first consumption.","Match fullBaseUrl and Duo redirect domains for correct cookie scope."],"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"}