{"record":{"id":"57b67f4edbe0c183","repo":"appwrite/appwrite","slug":"project-invalid-success-url","errorCode":"project_invalid_success_url","errorMessage":"Invalid redirect URL for OAuth success.","messagePattern":"Invalid redirect URL for OAuth success\\.","errorType":"exception","errorClass":"Appwrite\\Extend\\Exception","httpStatus":400,"severity":"error","filePath":"app/controllers/api/account.php","lineNumber":1566,"sourceCode":"            ], fn ($domain) => \\is_string($domain) && $domain !== '');\n\n            if (!empty($domains)) {\n                $rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [\n                    Query::equal('domain', \\array_values(\\array_unique($domains))),\n                    Query::equal('projectInternalId', [$project->getSequence()]),\n                    Query::limit(2)\n                ]));\n\n                foreach ($rules as $rule) {\n                    $allowedHostnames = $redirectValidator->getAllowedHostnames();\n                    $allowedHostnames[] = $rule->getAttribute('domain', '');\n                    $redirectValidator->setAllowedHostnames($allowedHostnames);\n                }\n            }\n        }\n\n        if ($devKey->isEmpty() && !$redirectValidator->isValid($state['success'])) {\n            throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);\n        }\n\n        if ($devKey->isEmpty() && !empty($state['failure']) && !$redirectValidator->isValid($state['failure'])) {\n            throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);\n        }\n        $failure = [];\n        if (!empty($state['failure'])) {\n            $failure = URLParser::parse($state['failure']);\n        }\n\n        $failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure) {\n            $exception = new Exception($type, $message, $code, $previous, params: $params);\n            if (!empty($failure)) {\n                $query = URLParser::parseQuery($failure['query']);\n                $query['error'] = json_encode([\n                    'message' => $exception->getMessage(),\n                    'type' => $exception->getType(),\n                    'code' => !\\is_null($code) ? $code : $exception->getCode(),","sourceCodeStart":1548,"sourceCodeEnd":1584,"githubUrl":"https://github.com/appwrite/appwrite/blob/cd368e707d4b492bc5e8e9c0f8ecbc5b741c4bf4/app/controllers/api/account.php#L1548-L1584","documentation":"Thrown by GET /v1/account/sessions/oauth2/:provider/redirect when the success redirect URL fails validation against the project's allowed hostnames (and no dev key is present). The redirect validator enforces allow-listed domains to prevent open-redirect abuse.","triggerScenarios":"success URL host not in the project's allowed hostname list; success URL omitted/malformed when required; cross-origin redirect to an unregistered domain.","commonSituations":"New frontend domain not added to Auth > Allowed hosts; localhost used but not allow-listed; success URL pointing to a third-party domain.","solutions":["Add the success URL's host to the project's allowed hostnames in Console > Auth > Allowed Hostnames.","Use a registered project domain or a listed localhost host.","If developing locally, use a dev key or add the local host to the allow-list."],"exampleFix":"// before\nawait account.createOAuth2Session('github', 'https://unregistered.example.com/success'); // throws\n\n// after: add unregistered.example.com to allowed hostnames, then\nawait account.createOAuth2Session('github', 'https://app.example.com/success');","handlingStrategy":"validation","validationCode":"const allowedHosts = ['app.example.com','localhost'];\nfunction successUrlOk(url) {\n  const host = new URL(url).hostname;\n  return allowedHosts.includes(host);\n}\nif (!successUrlOk(success)) throw new Error('Register success host in Console');","typeGuard":"const isAllowedHost = (url, hosts) => hosts.includes(new URL(url).hostname);","tryCatchPattern":"try { await account.createOAuth2Session(p, success, failure); } catch (e) { if (e?.type === 'project_invalid_success_url') { promptRegisterHost(success); } else throw e; }","preventionTips":["Register every success-redirect host in Console > Auth > Allowed Hostnames.","Use the same host for success and failure redirects.","For local dev, allow-list localhost or use a dev key."],"tags":["account","oauth","redirect","validation","allowed-hosts","security"],"backgroundTag":null,"analyzedSha":"cd368e707d4b492bc5e8e9c0f8ecbc5b741c4bf4","analyzedAt":"2026-08-12T14:42:48.571Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}