{"record":{"id":"a6088abb88c678aa","repo":"passbolt/passbolt_api","slug":"an-authentication-token-state-is-required","errorCode":null,"errorMessage":"An authentication token state is required.","messagePattern":"An authentication token state is required\\.","errorType":"http","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoCallbackAuthenticationTokenService.php","lineNumber":116,"sourceCode":"        }\n    }\n\n    /**\n     * Assert the Duo callback authentication token state value.\n     *\n     * @param \\App\\Model\\Entity\\AuthenticationToken $authToken The callback authentication token\n     * @param string $duoState The Duo callback state\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\InternalErrorException if the callback authentication token does not have state defined\n     * @throws \\Cake\\Http\\Exception\\UnauthorizedException if the callback authentication token state value does not match the Duo callback state\n     */\n    private function assertDuoStateMatchesAuthenticationTokenState(\n        AuthenticationToken $authToken,\n        string $duoState\n    ): void {\n        $authTokenState = $authToken->getDataValue('state');\n        if (empty($authTokenState)) {\n            throw new InternalErrorException(__('An authentication token state is required.'));\n        }\n        if ($authTokenState !== $duoState) {\n            throw new UnauthorizedException(__('The Duo state should match the authentication token state.'));\n        }\n    }\n}\n","sourceCodeStart":98,"sourceCodeEnd":123,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoCallbackAuthenticationTokenService.php#L98-L123","documentation":"After consuming the token, assertDuoStateMatchesAuthenticationTokenState reads the token's custom 'state' payload via getDataValue('state'). Duo's OAuth-style flow requires a state value stored when the token was issued. If the stored state is empty, the server raises InternalErrorException because this indicates the token was created without the required state data — an internal invariant breach, not a user fault.","triggerScenarios":"A Duo callback authentication token exists and consumes fine, but its data payload has no 'state' key or an empty value — typically because the token was created by code that did not set the state data, or the data column was truncated/lost.","commonSituations":"Tokens created before the Duo state feature was introduced still sitting in the table; a custom or older token-generation path that omits the data field; manual database inserts during testing without the data payload.","solutions":["Regenerate the token via the proper Duo setup/verify start service so the state is stored in the token data","Inspect the authentication_tokens.data column for the token and confirm it contains JSON with a non-empty state","Ensure the token-issuing service passes the duo state into the token creation data payload","Clear stale pre-upgrade tokens (e.g. via the MFA settings reset) and restart the flow"],"exampleFix":"// before (issuing code)\n$token = $authenticationTokens->generate($userId, AuthenticationToken::TYPE_MFA_SETUP);\n// after\n$token = $authenticationTokens->generate(\n    $userId,\n    AuthenticationToken::TYPE_MFA_SETUP,\n    ['state' => $duoState]\n);","handlingStrategy":"validation","validationCode":"$data = json_decode($tokenEntity->data ?? '', true) ?? [];\nif (empty($data['state'])) {\n    throw new \\RuntimeException('Token issued without Duo state; regenerate it.');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $authToken = $service->consumeAndVerifyAuthenticationToken($uac, $type, $token, $state);\n} catch (\\Cake\\Http\\Exception\\InternalErrorException $e) {\n    $this->log('Token missing Duo state: ' . $e->getMessage());\n    // regenerate the token through the proper Duo start service\n}","preventionTips":["Always issue Duo callback tokens with the state stored in the token data payload","Treat empty-state tokens as legacy/stale and regenerate them","Add a unit test asserting the issuing service stores the state"],"tags":["internal-error","mfa","duo","state","data-payload"],"backgroundTag":"internal-invariant-violation","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}