{"record":{"id":"f4019148c56193e2","repo":"infiniflow/ragflow","slug":"login-failed-missing-authorization-header","errorCode":null,"errorMessage":"Login failed: missing Authorization header","messagePattern":"Login failed: missing Authorization header","errorType":"exception","errorClass":"AuthException","httpStatus":401,"severity":"error","filePath":"admin/client/user.py","lineNumber":75,"sourceCode":"    raise AuthException(f\"Register failed: {msg}\")\n\n\ndef login_user(client: HttpClient, server_type: str, email: str, password: str) -> str:\n    password_enc = encrypt_password(password)\n    payload = {\"email\": email, \"password\": password_enc}\n    if server_type == \"admin\":\n        response = client.request(\"POST\", \"/admin/login\", use_api_base=True, auth_kind=None, json_body=payload)\n    else:\n        response = client.request(\"POST\", \"/auth/login\", use_api_base=True, auth_kind=None, json_body=payload)\n    try:\n        res = response.json()\n    except Exception as exc:\n        raise AuthException(f\"Login failed: invalid JSON response ({exc})\") from exc\n    if res.get(\"code\") != 0:\n        raise AuthException(f\"Login failed: {res.get('message')}\")\n    token = response.headers.get(\"Authorization\")\n    if not token:\n        raise AuthException(\"Login failed: missing Authorization header\")\n    return token\n","sourceCodeStart":57,"sourceCodeEnd":77,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/admin/client/user.py#L57-L77","documentation":"Thrown by the RAGFlow admin client (admin/client/user.py:75) after a successful login POST to /admin/login or /auth/login: the JSON body reported code==0, but the HTTP response carried no 'Authorization' response header, which is where the server (via sync_construct_response(auth=user.get_id())) returns the session token. The client treats a missing token as a fatal login failure even though the login itself succeeded. It is an AuthException raised client-side, not an HTTP error status.","triggerScenarios":"Calling RagFlowAdminClient login (client.request('POST', '/admin/login'|'/auth/login', auth_kind=None, json_body=payload)) where the response body has code==0 but the Authorization response header is absent. Happens when: (1) server_type is wrong so the request hits /auth/login on a deployment that does not echo the token header; (2) a reverse proxy (nginx with 'proxy_hide_header Authorization' or underscore/header filtering) strips the response header; (3) the server build predates the header-echo behavior in sync_construct_response.","commonSituations":"Running an older ragflow server against a newer admin client, pointing the client at the wrong base URL or server_type ('admin' vs regular auth), or deploying behind a proxy/gateway that filters Authorization headers in both directions. Also seen when a custom WSGI middleware drops headers on login responses.","solutions":["Verify server_type matches the target deployment (admin server vs standard API) so login POSTs to /admin/login which is guaranteed to echo the token header.","Curl the login endpoint directly and confirm the response includes the Authorization header; if absent, upgrade the RAGFlow server so sync_construct_response sets it.","If a reverse proxy sits in front, ensure it forwards response headers (no proxy_hide_header/proxy_pass_header misconfig for Authorization).","As a last resort, extract the token from the JSON body (resp access_token) instead of the header, mirroring what the server sets in user.access_token."],"exampleFix":"# before\nresponse = client.request(\"POST\", \"/admin/login\", use_api_base=True, auth_kind=None, json_body=payload)\ntoken = response.headers.get(\"Authorization\")\nif not token:\n    raise AuthException(\"Login failed: missing Authorization header\")\n\n# after - fall back to body token set by login_admin()\nres = response.json()\ntoken = response.headers.get(\"Authorization\") or f\"Bearer {res['data']['access_token']}\"\nif not token:\n    raise AuthException(\"Login failed: missing Authorization header\")","handlingStrategy":"try-catch","validationCode":"import requests\nr = requests.post(f\"{base_url}/admin/login\", json=payload)\nbody = r.json()\nhas_header = \"Authorization\" in r.headers\nhas_body_token = isinstance(body.get(\"data\"), dict) and body[\"data\"].get(\"access_token\")\nif body.get(\"code\") == 0 and not (has_header or has_body_token):\n    raise RuntimeError(\"Server does not return a login token; upgrade server or fix proxy header forwarding\")","typeGuard":"def login_response_has_token(response) -> bool:\n    if response.headers.get(\"Authorization\"):\n        return True\n    try:\n        data = response.json().get(\"data\")\n    except ValueError:\n        return False\n    return isinstance(data, dict) and bool(data.get(\"access_token\"))","tryCatchPattern":"from admin.client.user import AuthException\ntry:\n    token = login(client, payload)\nexcept AuthException as e:\n    if \"missing Authorization header\" in str(e):\n        # token echo broken: fix proxy/server, or read data.access_token from body\n        raise\n    raise","preventionTips":["Pin client and server to matching RAGFlow versions so the Authorization response-header contract holds.","Smoke-test login once at deploy time and assert the token header is present before running batch jobs.","Document proxy config: Authorization must be passed through in responses, not only requests."],"tags":["authentication","http-headers","admin-client","login"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}