{"record":{"id":"9c883462760de12a","repo":"passbolt/passbolt_api","slug":"the-domain-is-invalid","errorCode":null,"errorMessage":"The domain is invalid.","messagePattern":"The domain is invalid\\.","errorType":"exception","errorClass":"Passbolt\\JwtAuthentication\\Error\\Exception\\Challenge\\InvalidDomainException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/JwtAuthentication/src/Authenticator/GpgJwtAuthenticator.php","lineNumber":444,"sourceCode":"     */\n    public function assertVersion(mixed $version): void\n    {\n        if (!isset($version) || !is_string($version) || $version !== self::PROTOCOL_VERSION) {\n            throw new Exception(__('The version is invalid.'));\n        }\n    }\n\n    /**\n     * Assert domain\n     *\n     * @param mixed $domain domain\n     * @return void\n     * @throws \\Passbolt\\JwtAuthentication\\Error\\Exception\\Challenge\\InvalidDomainException if domain is invalid\n     */\n    public function assertDomain(mixed $domain): void\n    {\n        if (!isset($domain) || !is_string($domain)) {\n            throw new InvalidDomainException(__('The domain is invalid.'));\n        }\n\n        if (rtrim($domain, '/') !== rtrim(Router::url('/', true), '/')) {\n            $expect = rtrim(Router::url('/', true));\n            $got = rtrim($domain, '/');\n            throw new InvalidDomainException(__('The domain is invalid. Expected: {0} and got {1}', $expect, $got));\n        }\n    }\n\n    /**\n     * @return \\App\\Utility\\OpenPGP\\OpenPGPBackend\n     */\n    public function getGpg(): OpenPGPBackend\n    {\n        return $this->gpg;\n    }\n\n    /**","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/JwtAuthentication/src/Authenticator/GpgJwtAuthenticator.php#L426-L462","documentation":"assertDomain verifies the challenge's domain field matches the server's own full base URL (Router::url('/', true)) modulo trailing slashes. InvalidDomainException is thrown (and may trigger email alerts) when the domain is missing, non-string, or points to a different host — a common anti-phishing check in the GPG auth protocol.","triggerScenarios":"POST /auth/jwt/login where the challenge's domain is absent/not a string, or differs from the configured App.fullBaseUrl: client used http vs https, localhost vs production host, a different port, or a trailing-path mismatch.","commonSituations":"App.fullBaseUrl misconfigured (defaulting to localhost) while clients reach the server via a real domain; reverse proxy stripping/altering the scheme; client SDK auto-detecting the API URL from a different base; accessing via IP while fullBaseUrl is the hostname.","solutions":["Set passbolt App.fullBaseUrl to the exact public URL clients use (scheme + host + port, no trailing slash issues)","Make the client build the challenge domain from the same base URL it uses to reach the API","Fix reverse proxy headers (X-Forwarded-Proto/Host) or disable auto scheme detection so Router::url full matches reality","Read the 'Expected: X and got Y' message in the response/logs and align the client to X","Avoid accessing the server through aliases (IP, internal hostname) that differ from fullBaseUrl"],"exampleFix":"// before config/app.php\n'fullBaseUrl' => 'http://localhost',\n// after\n'fullBaseUrl' => 'https://passbolt.example.com'; // must equal the domain field clients put in the challenge","handlingStrategy":"validation","validationCode":"const apiBase = new URL(apiUrl).origin + '/';\nif (rtrim(challenge.domain) !== rtrim(apiBase)) challenge.domain = apiBase;","typeGuard":"function domainMatches(c, base) { return typeof c.domain === 'string' && c.domain.replace(/\\/+$/, '') === base.replace(/\\/+$/, ''); }","tryCatchPattern":"try { await login(challenge); } catch (e) { if (/domain is invalid. Expected/.test(e.message)) { const expected = parseExpected(e.message); await login({ ...challenge, domain: expected }); } }","preventionTips":["Derive the challenge domain from the same base URL used for API calls","Set App.fullBaseUrl correctly (scheme/host/port) before client rollouts","Fix proxy headers so the server sees the public URL","Strip trailing slashes consistently on both sides","Avoid mixing http/https or IP/hostname between config and clients"],"tags":["domain","challenge","jwt","configuration"],"backgroundTag":"invalid-url-format","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"}