{"record":{"id":"570700090385314d","repo":"Significant-Gravitas/AutoGPT","slug":"callback-url-origin-is-not-allowed-allowed-origin","errorCode":null,"errorMessage":"Callback URL origin is not allowed. Allowed origins: {settings.config.external_oauth_callback_origins}","messagePattern":"Callback URL origin is not allowed\\. Allowed origins: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"autogpt_platform/backend/backend/api/external/v1/integrations.py","lineNumber":338,"sourceCode":"    provider: Annotated[str, Path(title=\"The OAuth provider\")],\n    request: OAuthInitiateRequest,\n    auth: APIAuthorizationInfo = Security(\n        require_permission(APIKeyPermission.MANAGE_INTEGRATIONS)\n    ),\n) -> OAuthInitiateResponse:\n    \"\"\"\n    Initiate an OAuth flow for an external application.\n\n    This endpoint allows external apps to start an OAuth flow with a custom\n    callback URL. The callback URL must be from an allowed origin configured\n    in the platform settings.\n\n    Returns a login URL to redirect the user to, along with a state token\n    for CSRF protection.\n    \"\"\"\n    # Validate callback URL\n    if not validate_callback_url(request.callback_url):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=(\n                f\"Callback URL origin is not allowed. \"\n                f\"Allowed origins: {settings.config.external_oauth_callback_origins}\",\n            ),\n        )\n\n    # Validate provider\n    try:\n        provider_name = ProviderName(provider)\n    except ValueError:\n        # Check if it's a dynamically registered provider\n        if provider not in HANDLERS_BY_NAME:\n            raise HTTPException(\n                status_code=status.HTTP_404_NOT_FOUND,\n                detail=f\"Provider '{provider}' not found\",\n            )\n        provider_name = provider","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/external/v1/integrations.py#L320-L356","documentation":"Raised (HTTP 400) by the external OAuth initiate endpoint when `validate_callback_url` rejects the supplied `callback_url`. Validation extracts the origin (`scheme://netloc`) and requires an exact match against `settings.config.external_oauth_callback_origins`; localhost is allowed with any port only if some allowed origin also uses localhost. Malformed URLs also fail (the parser wraps everything in try/except returning False).","triggerScenarios":"POST `/api/external-api/v1/integrations/{provider}/oauth/authorize` with a `callback_url` whose origin (scheme + host + port) is not listed in `external_oauth_callback_origins` — e.g. `http://localhost:5173/callback` when only `https://app.example.com` is allowed, or an `http` vs `https` mismatch, or a URL with a typo in the host.","commonSituations":"Local development against a remote/staging backend whose allow list only contains production origins; deploying an external app on a new domain without updating the platform setting; using `127.0.0.1` instead of `localhost` (hostname check is literal); trailing-slash or port differences between the allow-list entry and the actual callback.","solutions":["Add your application's exact origin (scheme + host + port, no path) to `external_oauth_callback_origins` in platform settings and retry.","For local dev, either point the callback at an allowed localhost origin or add your dev origin to the allow list.","Double-check scheme (`https` vs `http`) and port; matching is exact string equality on the origin.","Confirm the URL is well-formed — `validate_callback_url` returns False for unparseable URLs."],"exampleFix":"# before\nPOST /integrations/github/oauth/authorize\n{\"callback_url\": \"http://my-app.dev:3000/auth/callback\"}  # 400: origin not allowed\n\n# after: add to platform config (settings.config.external_oauth_callback_origins)\nexternal_oauth_callback_origins=[\"http://my-app.dev:3000\", \"https://app.example.com\"]","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\nALLOWED = set(get_platform_setting(\"external_oauth_callback_origins\"))\n\ndef callback_allowed(url: str) -> bool:\n    p = urlparse(url)\n    origin = f\"{p.scheme}://{p.netloc}\"\n    return origin in ALLOWED or (p.hostname == \"localhost\" and any(urlparse(a).hostname == \"localhost\" for a in ALLOWED))\n\nassert callback_allowed(callback_url), \"origin not in external_oauth_callback_origins\"","typeGuard":null,"tryCatchPattern":"try:\n    client.post(f\"/integrations/{provider}/oauth/authorize\", json={\"callback_url\": callback_url})\nexcept HTTPError as e:\n    if e.response.status_code == 400 and \"origin is not allowed\" in e.response.text:\n        raise ConfigError(\"Add origin to external_oauth_callback_origins\") from e\n    raise","preventionTips":["Derive the callback URL from configuration, not string concatenation, so origin never drifts.","Keep the allow list in the same config pipeline as the app domain (change them together).","Remember matching is exact: scheme, host, and port must all match; localhost requires a localhost entry."],"tags":["oauth","callback-url","validation","configuration"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}