{"record":{"id":"f88eb6826ca46f01","repo":"passbolt/passbolt_api","slug":"the-duo-authentication-origin-endpoint-does-not-match-the","errorCode":null,"errorMessage":"The duo authentication origin endpoint does not match the organization setting duo hostname.","messagePattern":"The duo authentication origin endpoint does not match the organization setting duo hostname\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoVerifyDuoCodeService.php","lineNumber":125,"sourceCode":"        return $duoAuthenticationData;\n    }\n\n    /**\n     * Assert that the origin response endpoint is a known Duo response endpoint.\n     *\n     * @see https://duo.com/docs/oauthapi\n     * @param string $duoAuthenticationDetailIss Duo endpoint from callback\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\UnauthorizedException If the duo authentication origin endpoint (iss) does not match the duo hostname\n     * defined in the organization settings.\n     */\n    private function assertDuoAuthenticationEndpoint(string $duoAuthenticationDetailIss): void\n    {\n        $duoApiHostname = MfaOrgSettings::get()->getDuoOrgSettings()->getDuoApiHostname();\n        $expectedIss = \"https://$duoApiHostname/oauth/v1/token\";\n        if ($duoAuthenticationDetailIss !== $expectedIss) {\n            $msg = __('The duo authentication origin endpoint does not match the organization setting duo hostname.');\n            throw new UnauthorizedException($msg);\n        }\n    }\n\n    /**\n     * Assert that the Duo subscriber who authenticated matches the user's username.\n     *\n     * @see https://duo.com/docs/oauthapi\n     * @param string $duoSubscriber Duo subscriber from callback\n     * @param string $operatorUsername Operator username\n     * @return void\n     * @throws \\Cake\\Http\\Exception\\UnauthorizedException if the duo authentication subscriber does not match the operator username\n     */\n    private function assertDuoAuthenticationSubscriber(string $duoSubscriber, string $operatorUsername): void\n    {\n        $verifySubscriber = Configure::read(self::PASSBOLT_SECURITY_MFA_DUO_VERIFY_SUBSCRIBER);\n        if ($verifySubscriber === true && mb_strtolower($duoSubscriber) !== mb_strtolower($operatorUsername)) {\n            $msg = __('The duo authentication subscriber does not match the operator username.');\n            throw new UnauthorizedException($msg);","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoVerifyDuoCodeService.php#L107-L143","documentation":"Thrown by assertDuoAuthenticationEndpoint() after a successful Duo code exchange when the 'iss' claim of the returned authentication details does not equal \"https://<duoApiHostname>/oauth/v1/token\" built from the organization settings. It protects against the response originating from a host other than the configured Duo API hostname.","triggerScenarios":"Verifying a duo code where the token's iss claim points to a different hostname than the org setting (e.g. settings changed between login start and verification, or a spoofed/mismatched token).","commonSituations":"Admin changed the Duo API hostname in org settings while users had in-flight authentications; trailing slash or scheme mismatch in configured hostname; load balancer rewriting the host; man-in-the-middle attempts.","solutions":["Confirm the Duo API hostname in MFA org settings exactly matches the hostname that issued the token (no trailing slash, https scheme).","Ask users to restart the MFA flow after any hostname configuration change.","Compare the received iss value (from the wrapped details) with the expected value in logs to spot the mismatch.","Ensure no proxy rewrites the Duo host header."],"exampleFix":"// before: hostname stored with trailing slash\n'Duo' => ['apiHostname' => 'sso-abc.sso.duosecurity.com/']\n// after\n'Duo' => ['apiHostname' => 'sso-abc.sso.duosecurity.com']","handlingStrategy":"validation","validationCode":"$hostname = MfaOrgSettings::get()->getDuoOrgSettings()->getDuoApiHostname();\n$valid = is_string($hostname) && preg_match('/^[a-z0-9.-]+\\.duosecurity\\.com$/', rtrim($hostname, '/'));","typeGuard":null,"tryCatchPattern":"try {\n    $details = $service->verify($uac, $mfaToken, $duoCode);\n} catch (UnauthorizedException $e) {\n    Log::warning('Duo iss mismatch: ' . $e->getMessage());\n    throw $e;\n}","preventionTips":["Store the Duo api hostname without trailing slash or scheme.","After changing the hostname, invalidate in-flight MFA sessions.","Monitor iss-mismatch events — they may indicate tampering."],"tags":["mfa","duo","authentication","hostname-mismatch"],"backgroundTag":"unexpected-response-shape","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"}