{"record":{"id":"55e2712bbf2405be","repo":"infiniflow/ragflow","slug":"failed-to-exchange-authorization-code-for-token","errorCode":null,"errorMessage":"Failed to exchange authorization code for token: {e}","messagePattern":"Failed to exchange authorization code for token: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"api/apps/auth/oauth.py","lineNumber":79,"sourceCode":"        return authorization_url\n\n    def exchange_code_for_token(self, code):\n        \"\"\"\n        Exchange authorization code for access token.\n        \"\"\"\n        try:\n            payload = {\"client_id\": self.client_id, \"client_secret\": self.client_secret, \"code\": code, \"redirect_uri\": self.redirect_uri, \"grant_type\": \"authorization_code\"}\n            response = sync_request(\n                \"POST\",\n                self.token_url,\n                data=payload,\n                headers={\"Accept\": \"application/json\"},\n                timeout=self.http_request_timeout,\n            )\n            response.raise_for_status()\n            return response.json()\n        except Exception as e:\n            raise ValueError(f\"Failed to exchange authorization code for token: {e}\")\n\n    async def async_exchange_code_for_token(self, code):\n        \"\"\"\n        Async variant of exchange_code_for_token using httpx.\n        \"\"\"\n        payload = {\n            \"client_id\": self.client_id,\n            \"client_secret\": self.client_secret,\n            \"code\": code,\n            \"redirect_uri\": self.redirect_uri,\n            \"grant_type\": \"authorization_code\",\n        }\n        try:\n            response = await async_request(\n                \"POST\",\n                self.token_url,\n                data=payload,\n                headers={\"Accept\": \"application/json\"},","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/apps/auth/oauth.py#L61-L97","documentation":"OAuthClient.exchange_code_for_token POSTs grant_type=authorization_code with client_id, client_secret, code, redirect_uri to token_url and wraps any failure in ValueError('Failed to exchange authorization code for token: {e}') (api/apps/auth/oauth.py:79). The original exception text is preserved, so the message usually embeds the provider's HTTP status or transport error.","triggerScenarios":"redirect_uri differs byte-for-byte from the one registered with the provider (trailing slash, http vs https, port); client_secret wrong or rotated; the authorization code was already used once or expired (typically ~1-10 min); code from a different client_id; network/TLS failure reaching token_url; provider returns non-JSON on error making response.json() raise.","commonSituations":"Local development behind a proxy where the externally visible URL differs from redirect_uri; secrets rotated without updating RAGFlow config; double callback firing (browser retry, duplicate route) consuming the one-time code; clock/token endpoint typos in hand-written OAuth config.","solutions":["Compare the redirect_uri sent in the token request with the one in the initial authorization request and the provider's registered callback - they must match exactly.","Verify client_id/client_secret are current and copied without whitespace.","Ensure the code is used exactly once and immediately; inspect for duplicate callback invocations (logs show two exchanges with the same code).","Curl the token endpoint manually with the same payload to see the provider's raw error (invalid_grant, invalid_client, etc.)."],"exampleFix":"# reproduce to see the provider's real error\ncurl -X POST https://provider.example/oauth/token \\\n  -d grant_type=authorization_code -d client_id=ID -d client_secret=SECRET \\\n  -d code=THE_CODE -d redirect_uri='https://your-app/v1/auth/oauth/callback'","handlingStrategy":"try-catch","validationCode":"def exchange_precheck(provider_cfg, redirect_uri):\n    assert provider_cfg[\"client_id\"] and provider_cfg[\"client_secret\"]\n    assert provider_cfg[\"token_url\"].startswith(\"https://\")\n    assert redirect_uri == provider_cfg[\"registered_redirect_uri\"]  # byte-exact","typeGuard":null,"tryCatchPattern":"try:\n    token = client.exchange_code_for_token(code)\nexcept ValueError as e:\n    msg = str(e)\n    if \"invalid_grant\" in msg:\n        restart_authorization()   # code expired/used - get a fresh one\n    elif \"invalid_client\" in msg:\n        raise ConfigError(\"OAuth client secret wrong - update provider config\")\n    else:\n        raise","preventionTips":["Register one canonical redirect_uri and build both authorization and token requests from the same constant.","Never reuse an authorization code; guard against duplicate callback deliveries.","Rotate secrets on schedule and update RAGFlow config atomically.","Test the token endpoint with curl when adding a new provider."],"tags":["auth","oauth","token","configuration","http"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}